Difference between revisions of "Module:Weather/sandbox"

From blackwiki
Jump to navigation Jump to search
blackwiki>Erutuon
m (duh: make the function accessible to template)
m (85 revisions imported)
 
(27 intermediate revisions by one other user not shown)
Line 1: Line 1:
export = {}
+
local p = {}
degree = "°" -- used by add_unit_names()
+
 
minus = "−" -- used by makeRow() and makeTable()
+
require('Module:No globals')
thinSpace = mw.ustring.char(0x2009) -- used by makeCell()
+
 
 +
local degree = "°" -- used by addUnitNames()
 +
local minus = "−" -- used by makeRow() and makeTable()
 +
local thinSpace = mw.ustring.char(0x2009) -- used by makeCell()
 +
 
 +
local precision, decimals
 +
 
 +
-- if not empty
 +
local function ine(var)
 +
var = tostring(var)
 +
if var == "" then
 +
return nil
 +
else
 +
return var
 +
end
 +
end
  
 
-- Error message handling
 
-- Error message handling
message = ""
+
local message = ""
  
local function add_message(new_message)
+
local function addMessage(newMessage)
if show then
+
if ine(message) then
if check_for_string(message) then
+
message = message .. " " .. newMessage
message = message .. " " .. new_message
+
else
else
+
message = "Notices: " .. newMessage
message = "Notices: " .. new_message
 
end
 
 
end
 
end
 +
end
 +
 +
local function monospace(str)
 +
return '<span style="background-color: #EEE; font-family: monospace;">' .. str .. '</span>'
 
end
 
end
  
 
-- Input and output parameters
 
-- Input and output parameters
local function get_format (frame)
+
local function getFormat(inputParameter, outputParameter, palette, messages)
local input_parameter = frame.args.input
+
local length, inputUnit, outputUnit, palette, show, cellFormat
local output_parameter = frame.args.output
 
 
 
if input_parameter == nil then
+
if inputParameter == nil then
error("Please provide the number of values and a unit in the input parameter")
+
error('Please provide the number of values and a unit in the input parameter')
 
else
 
else
length = tonumber(string.match(input_parameter, "(%d+)")) -- Find digits in the input parameter.
+
-- Find as many as two digits in the input parameter.
input_unit = string.match(input_parameter, "([CF])") -- C or F
+
length = tonumber(string.match(inputParameter, "(%d%d?)"))  
if string.find(input_parameter, "[^CF%d%s]") then
+
if not length then
add_message("There are extraneous characters in the <span style=\"background-color: #EEE; font-family: monospace;\">output</span> parameter.")
+
length = 13
 +
addMessage('getFormat has not found a length value in the input parameter; length defaults to "13"')
 +
end
 +
 +
-- Find C or F, but not both
 +
if string.find(inputParameter, "C") and string.find(inputParameter, "F") then
 +
error("Input unit must be either C (Celsius) or F (Fahrenheit)")
 +
else
 +
inputUnit = string.match(inputParameter, "([CF])") or error("Please provide an input unit in the input parameter: F for Fahrenheit or C for Celsius", 0)
 +
end
 +
 +
if inputUnit == "C" then
 +
outputUnit = "F"
 +
else
 +
outputUnit = "C"
 +
end
 +
 +
-- Make sure nothing except C, F, numbers, or spaces is in the input parameter.
 +
if string.find(inputParameter, "[^CF%d%s]") then
 +
addMessage("There are extraneous characters in the " .. monospace("output") .. " parameter.")
 
end
 
end
 
end
 
end
 
 
if input_unit == "C" then
+
if outputParameter == nil then
output_unit = "F"
+
-- Since there are default values, the module will still generate output with an empty output parameter.
elseif input_unit == "F" then
+
addMessage("No output format has been provided in the " .. monospace("output") .. " parameter, so default values will be used.")
output_unit = "C"
 
 
else
 
else
error ("Please provide an input unit in the input parameter: F for Fahrenheit or C for Celsius", 0)
+
cellFormat = {}
end
+
for i, unit in require("Module:StringTools").imatch(outputParameter, "[CF]") do
+
cellFormat[i] = unit
if length == nil then
+
if i > 2 then
error ("get_format has not found a length value in the input parameter")
 
end
 
 
if output_parameter == nil then
 
add_message("No output format has been provided in the <span style=\"background-color: #EEE; font-family: monospace;\">output</span> parameter.")
 
else
 
cell_format = {}
 
local n = 1
 
for unit in output_parameter:gmatch("[CF]") do
 
cell_format[n] = unit
 
n = n + 1
 
if n > 2 then
 
 
break
 
break
 
end
 
end
 
end
 
end
local function set_format(key, formatVariable, formatValue1, formatValue2)
+
local function setFormat(key, variable, value)
if string.find(output_parameter, key) then
+
if string.find(outputParameter, key) then
cell_format[formatVariable] = formatValue1
+
cellFormat[variable] = value
 
else
 
else
cell_format[formatVariable] = formatValue2
+
cellFormat[variable] = not value
 
end
 
end
 
end
 
end
if cell_format[1] then
+
if cellFormat[1] then
cell_format.first = cell_format[1]
+
cellFormat.first = cellFormat[1]
 
else
 
else
error("C or F not found in output parameter")
+
error('C or F not found in output parameter')
 
end
 
end
if cell_format[2] == nil then
+
if cellFormat[2] == nil then
cell_format["convert_units"] = "no"
+
cellFormat["convertUnits"] = false
 
else
 
else
if cell_format[2] == cell_format[1] then
+
if cellFormat[2] == cellFormat[1] then
error("There should not be two of the same unit name in the output parameter.")
+
error('There should not be two of the same unit name in the output parameter.')
 
else
 
else
cell_format["convert_units"] = "yes"
+
cellFormat["convertUnits"] = true
 
end
 
end
 
end
 
end
set_format("unit", "unit_names", "yes", "no")
+
setFormat("unit", "unitNames", true)
set_format("no ?color", "color", "no", "yes")
+
setFormat("no ?color", "color", false)
set_format("sort", "sortable", "yes", "no")
+
setFormat("sort", "sortable", true)
set_format("full ?size", "small_font", "no", "yes")
+
setFormat("full ?size", "smallFont", false)
set_format("no ?brackets", "brackets", "no", "yes")
+
setFormat("no ?brackets", "brackets", false)
set_format("round", "decimals", "0", "")
+
setFormat("round", "decimals", "0", "")
if string.find(output_parameter, "line break") then
+
if string.find(outputParameter, "line break") then
cell_format["line_break"] = "yes"
+
cellFormat["lineBreak"] = true
elseif string.find(output_parameter, "one line") then
+
elseif string.find(outputParameter, "one line") then
cell_format["line_break"] = "no"
+
cellFormat["lineBreak"] = false
 
else
 
else
cell_format["line_break"] = "auto"
+
cellFormat["lineBreak"] = "auto"
 
end
 
end
if string.find(output_parameter, "one line") and string.find(output_parameter, "line break") then
+
if string.find(outputParameter, "one line") and
error("Place either \"one line\" or \"line break\" in the output parameter, not both")
+
string.find(outputParameter, "line break") then
 +
error('Place either "one line" or "line break" in the output parameter, not both')
 
end
 
end
 
end
 
end
if frame.args.palette == nil then
 
palette = "cool2avg"
 
else
 
palette = frame.args.palette
 
end
 
 
 
if frame.args.messages == "show" then
+
palette = palette or "cool2avg"
show = true
+
else
+
show = messages == "show"
show = false
 
end
 
 
 
return length, input_unit, output_unit
+
return {
 +
length = length, inputUnit = inputUnit, outputUnit = outputUnit,
 +
cellFormat = cellFormat, show = show, palette = palette
 +
}
 
end
 
end
  
-- Number and string-handling functions
+
-- Math functions
local function check_for_number(value)
 
return type(tonumber(value)) == "number"
 
end
 
 
 
function check_for_string(string)
 
string = tostring(string)
 
return string ~= "" and string ~= nil
 
end
 
  
 
local function round(value, decimals)
 
local function round(value, decimals)
 
value = tonumber(value)
 
value = tonumber(value)
 
if type(value) == "number" then
 
if type(value) == "number" then
local string = string.format("%." .. decimals .. "f", value)
+
return string.format("%." .. decimals .. "f", value)
return string
+
else
elseif value == nil then
+
error("Format was asked to operate on " .. tostring(value) .. ", which cannot be converted to a number.", 2)
value = "nil"
 
add_message("Format was asked to operate on " .. value .. ", which cannot be converted to a number.", 2)
 
 
return ""
 
return ""
 
end
 
end
 
end
 
end
  
local function convert(value, decimals, unit) -- Unit is the unit being converted from. It defaults to input_unit.
+
local function convert(value, unit, decimals) -- Unit is the unit being converted from.
 
if not unit then
 
if not unit then
unit = input_unit
+
error("No unit supplied to convert.", 2)
 
end
 
end
if check_for_number(value) then
+
if tonumber(value) then
 
local value = tonumber(value)
 
local value = tonumber(value)
 
if unit == "C" then
 
if unit == "C" then
add_message(value .. " " .. degree .. unit .. " was converted.")
 
 
return round(value * 9/5 + 32, decimals)
 
return round(value * 9/5 + 32, decimals)
 
elseif unit == "F" then
 
elseif unit == "F" then
add_message(value .. " " .. degree .. unit .. " was converted.")
 
 
return round((value - 32) * 5/9, decimals)
 
return round((value - 32) * 5/9, decimals)
 
else
 
else
Line 147: Line 154:
 
end
 
end
 
else
 
else
return "" -- Setting result to empty string if value is not a number avoids concatenation errors.
+
-- to avoid concatenation errors
 +
return ""
 
end
 
end
 
end
 
end
  
-- Input parsing
+
-- Stick numbers into array. Find out if any have decimals.
function make_array(parameter, array, frame)
+
-- Throw an error if any are invalid.
local array = {}
+
local function _makeArray(format)
local n = 1
+
return function(parameter)
for number in parameter:gmatch("%-?%d+%.?%d?") do
+
if not parameter then
local number = number
+
return nil
if n == 1 then
+
end
local decimals = number:match("%.(%d+)")
+
local array = {}
if decimals == nil then
+
-- If there are multiple parameters for numbers, and the first doesn't have
precision = "0"
+
-- decimals, the rest will have their decimals rounded off.
else
+
format.precision = format.precision or parameter:find("%d%.%d") and "1" or "0"
precision = #decimals
+
 +
local numbers = mw.text.split(parameter, "%s+")
 +
if #numbers ~= format.length then
 +
addMessage('There are not ' .. format.length .. ' values in the ' .. parameter .. ' parameter.')
 +
end
 +
 +
for i, number in ipairs(numbers) do
 +
if not number:find("^%-?%d%d?%d?.?(%d?)$") then
 +
error('The number "' .. number .. '" does not fit the expected pattern.')
 
end
 
end
 +
 +
table.insert(array, number)
 
end
 
end
table.insert(array, n, number)
+
n = n + 1
+
return array
if n > length then
 
break
 
end
 
end
 
if not array[length] then
 
add_message("There are not " .. length .. " values in the " .. parameter .. " parameter.")
 
end
 
return array, precision
 
end
 
 
 
function make_arrays(frame)
 
get_format(frame)
 
local parameter_a = frame.args.a
 
local parameter_b = frame.args.b
 
local parameter_c = frame.args.c
 
if parameter_a then
 
a = make_array(parameter_a, a, frame)
 
else
 
error("Please provide a set of numbers in parameter a")
 
end
 
if parameter_b then
 
b = make_array(parameter_b, b, frame)
 
else
 
add_message("There is no content in parameter <span style=\"background-color: #EEE; font-family: monospace;\">b</span>.")
 
end
 
if parameter_c then
 
c = make_array(parameter_c, c, frame)
 
else
 
add_message("There is no content in parameter <span style=\"background-color: #EEE; font-family: monospace;\">c</span>.")
 
 
end
 
end
return a, b, c
 
 
end
 
end
  
 
-- Color generation
 
-- Color generation
  
palettes = {
+
p.palettes = {
-- The first three arrays in each palette defines background color using a table of four numbers,
+
--[[
-- say { 11, 22, 33, 44 } (values in °C).
+
The first three arrays in each palette defines background color using a
-- That means the color is 0 below 11 and above 44, and is 255 from 22 to 33.
+
table of four numbers, say { 11, 22, 33, 44 } (values in °C).
-- The color rises from 0 to 255 between 11 and 22, and falls between 33 and 44.
+
That means that, on the scale from 0 (black) to 255 (saturated), the color
 +
is 0 below 11°C and above 44°C, and is 255 from 22°C to 33°C.
 +
The color rises from 0 to 255 between 11°C and 22°C, and falls from 255 to 0
 +
between 33°C and 44°C.
 +
]]
 
cool = {
 
cool = {
{ -42.75,  4.47, 41.5, 60  },
+
{ -42.75,  4.47, 41.5, 60  }, -- red
{ -42.75,  4.47,  4.5, 41.5 },
+
{ -42.75,  4.47,  4.5, 41.5 }, -- green
{ -90  , -42.78,  4.5, 23  },
+
{ -90  , -42.78,  4.5, 23  }, -- blue
white = { -23.3, 37.8 },
+
white = { -23.3, 37.8 }, -- background
 
},
 
},
 
cool2 = {
 
cool2 = {
Line 227: Line 219:
 
}
 
}
  
local function temperature_color(palette, value, out_rgb)
+
--[[ Return style for a table cell based on the given value which
--[[ Return style for a table cell based on the given value which
+
should be a temperature in °C. ]]
should be a temperature in °C. ]]
+
local function temperatureColor(palette, value, outRGB)
local background_color, text_color
+
local backgroundColor, textColor
 
value = tonumber(value)
 
value = tonumber(value)
if value == nil then
+
if not value then
background_color, text_color = 'FFF', '000'
+
backgroundColor, textColor = 'FFF', '000'
add_message("Value supplied to <span style=\"background-color: #EEE; font-family: monospace;\">temperature_color</span> is not recognized.")
+
addMessage("Value supplied to " .. monospace("temperatureColor") .. " is not recognized.")
 
else
 
else
 
local min, max = unpack(palette.white or { -23, 35 })
 
local min, max = unpack(palette.white or { -23, 35 })
 
if value < min or value >= max then
 
if value < min or value >= max then
text_color = 'FFF'
+
textColor = 'FFF'
else
+
-- Else nil.
text_color = '' -- This assumes that black text color is the default for most readers.
+
-- This assumes that black text color is the default for most readers.
 
end
 
end
  
local background_rgb = out_rgb or {}
+
local backgroundRGB = outRGB or {}
 
for i, v in ipairs(palette) do
 
for i, v in ipairs(palette) do
 
local a, b, c, d = unpack(v)
 
local a, b, c, d = unpack(v)
 
if value <= a then
 
if value <= a then
background_rgb[i] = 0
+
backgroundRGB[i] = 0
 
elseif value < b then
 
elseif value < b then
background_rgb[i] = (value - a) * 255 / (b - a)
+
backgroundRGB[i] = (value - a) * 255 / (b - a)
 
elseif value <= c then
 
elseif value <= c then
background_rgb[i] = 255
+
backgroundRGB[i] = 255
 
elseif value < d then
 
elseif value < d then
background_rgb[i] = 255 - ( (value - c) * 255 / (d - c) )
+
backgroundRGB[i] = 255 - ( (value - c) * 255 / (d - c) )
 
else
 
else
background_rgb[i] = 0
+
backgroundRGB[i] = 0
 
end
 
end
 
end
 
end
background_color = string.format('%02X%02X%02X', background_rgb[1], background_rgb[2], background_rgb[3])
+
backgroundColor = string.format('%02X%02X%02X', unpack(backgroundRGB))
end
 
if text_color == "" then
 
return background_color
 
else
 
return background_color, text_color
 
 
end
 
end
 +
return backgroundColor, textColor
 
end
 
end
  
local function color_CSS(background_color, text_color)
+
local function colorCSS(backgroundColor, textColor)
if background_color and text_color then
+
if backgroundColor and textColor then
return 'background: #' .. background_color .. '; color: #' .. text_color .. ';'
+
return 'background: #' .. backgroundColor .. '; color: #' .. textColor .. ';'
elseif background_color then
+
elseif backgroundColor then
return 'background: #' .. background_color .. ';'
+
return 'background: #' .. backgroundColor .. ';'
 
else
 
else
 
return ''
 
return ''
Line 277: Line 265:
 
end
 
end
  
local function temperature_color_CSS(palette, value, out_rgb)
+
local function temperatureColorCSS(palette, value, outRGB)
return color_CSS(temperature_color(palette, value, out_rgb))
+
return colorCSS(temperatureColor(palette, value, outRGB))
 
end
 
end
  
function temperature_CSS(value, unit, palette)
+
local function temperatureCSS(value, unit, palette)
local palette = palettes[palette] or palettes.cool
+
local palette = p.palettes[palette] or p.palettes.cool
 
local value = tonumber(value)
 
local value = tonumber(value)
 
if value == nil then
 
if value == nil then
error("The function <span style=\"background-color: #EEE; font-family: monospace;\">temperature_CSS</span> is receiving a nil value")
+
error("The function " .. monospace("temperatureCSS") .. " is receiving a nil value")
 
else
 
else
if unit == 'C' then
+
if unit == 'F' then
return color_CSS(temperature_color(palette, value))
+
value = convert(value, 'F', decimals)
elseif unit == 'F' then
+
elseif unit ~= 'C' then
return color_CSS(temperature_color(palette, convert(value, decimals, 'F')))
+
unitError(unit or "nil")
else
 
unit_error(unit or "nil")
 
 
end
 
end
 +
return colorCSS(temperatureColor(palette, value))
 
end
 
end
 
end
 
end
  
local function style_attribute(palette, value, out_rgb)
+
local function styleAttribute(palette, value, outRGB)
local font_size = "font-size: 85%;"
+
local fontSize = "font-size: 85%;"
local color = temperature_color_CSS(palette, value, out_rgb)
+
local color = temperatureColorCSS(palette, value, outRGB)
return 'style=\"' .. color .. ' ' .. font_size .. '\"'
+
return 'style=\"' .. color .. ' ' .. fontSize .. '\"'
 
end
 
end
  
function export.temperature_style(frame) -- used by Template:Average temperature table/color
+
local style_attribute = styleAttribute
local palette = palettes[frame.args.palette] or palettes.cool
+
 
 +
--[=[
 +
Used by {{Average temperature table/row/C/sandbox}},
 +
{{Average temperature table/row/F/sandbox}},
 +
{{Average temperature table/row/C/sandbox}},
 +
{{Template:Avg temp row F/sandbox2}},
 +
{{Template:Avg temp row C/sandbox2}}.
 +
]=]
 +
function p.temperatureStyle(frame)
 +
local palette = p.palettes[frame.args.palette] or p.palettes.cool
 
local unit = frame.args.unit or 'C'
 
local unit = frame.args.unit or 'C'
 
local value = tonumber(frame.args[1])
 
local value = tonumber(frame.args[1])
if unit == 'C' then
+
if unit == 'F' then
return style_attribute(palette, value)
+
value = convert(value, 'F', 1)
elseif unit == 'F' then
+
elseif unit ~= 'C' then
return style_attribute(palette, convert(value, 'F'))
+
error('Unrecognized unit: ' .. unit)
else
 
unit_error(unit)
 
 
end
 
end
 +
return styleAttribute(palette, value)
 
end
 
end
 +
 +
p.temperature_style = p.temperatureStyle
  
 
--[[ ==== Cell, row, table generation ==== ]]
 
--[[ ==== Cell, row, table generation ==== ]]
local output_formats = {
+
local outputFormats = {
 
high_low_average_F =
 
high_low_average_F =
 
{ first = "F",
 
{ first = "F",
convert_units = "yes",
+
convertUnits = true,
unit_names = "no",
+
unitNames = false,
color = "yes",
+
color = true,
small_font = "yes",
+
smallFont = true,
sortable = "yes",
+
sortable = true,
 
decimals = "0",
 
decimals = "0",
brackets = "yes",
+
brackets = true,
line_break = "auto", },
+
lineBreak = "auto", },
 
high_low_average_C =
 
high_low_average_C =
 
{ first = "C",
 
{ first = "C",
convert_units = "yes",
+
convertUnits = true,
unit_names = "no",
+
unitNames = false,
color = "yes",
+
color = true,
small_font = "yes",
+
smallFont = true,
sortable = "yes",
+
sortable = true,
 
decimals = "0",
 
decimals = "0",
brackets = "yes",
+
brackets = true,
line_break = "auto", },
+
lineBreak = "auto", },
 
high_low_F =
 
high_low_F =
 
{ first = "F",
 
{ first = "F",
convert_units = "yes",
+
convertUnits = true,
unit_names = "no",
+
unitNames = false,
color = "no",
+
color = false,
small_font = "yes",
+
smallFont = true,
sortable = "no",
+
sortable = false,
 
decimals = "",
 
decimals = "",
brackets = "yes",
+
brackets = true,
line_break = "auto", },
+
lineBreak = "auto", },
 
high_low_C =
 
high_low_C =
 
{ first = "C",
 
{ first = "C",
convert_units = "yes",
+
convertUnits = true,
unit_names = "no",
+
unitNames = false,
color = "no",
+
color = false,
small_font = "yes",
+
smallFont = true,
sortable = "no",
+
sortable = false,
 
decimals = "0",
 
decimals = "0",
brackets = "yes",
+
brackets = true,
line_break = "auto", },
+
lineBreak = "auto", },
 
average_F =
 
average_F =
 
{ first = "F",
 
{ first = "F",
convert_units = "yes",
+
convertUnits = true,
unit_names = "no",
+
unitNames = false,
color = "yes",
+
color = true,
small_font = "yes",
+
smallFont = true,
sortable = "no",
+
sortable = false,
 
decimals = "0",
 
decimals = "0",
brackets = "yes",
+
brackets = true,
line_break = "auto", },
+
lineBreak = "auto", },
 
average_C =
 
average_C =
 
{ first = "C",
 
{ first = "C",
convert_units = "yes",
+
convertUnits = true,
unit_names = "no",
+
unitNames = false,
color = "yes",
+
color = true,
small_font = "yes",
+
smallFont = true,
sortable = "no",
+
sortable = false,
 
decimals = "0",
 
decimals = "0",
brackets = "yes",
+
brackets = true,
line_break = "auto", },
+
lineBreak = "auto", },
 
}
 
}
  
local function add_unit_names(value, unit)
+
local outputFormat
if not unit then unit = input_unit end
+
 
if output_format.unit_names == "yes" then
+
local function addUnitNames(value, yesOrNo, unit)
if check_for_string(value) then
+
if not unit then
return value .. "&nbsp;" .. degree .. unit
+
error("No unit supplied as argument 3 to addUnitNames", 2)
else
 
return value -- Don't add a unit name to an empty string
 
end
 
else
 
return value
 
 
end
 
end
 +
-- Don't add a unit name to an empty string
 +
value = yesOrNo == true and ine(value) and value .. "&nbsp;" .. degree .. unit or value
 +
return value
 
end
 
end
  
local function if_yes(parameter, realization1, realization2)
+
local function ifYes(parameter, realization1, realization2)
 +
local result
 
if realization1 then
 
if realization1 then
 
if realization2 then
 
if realization2 then
if parameter == "yes" then
+
result = parameter == true and { realization1, realization2 } or { "", "" }
parameter = { realization1, realization2 }
 
else
 
parameter = { "", "" }
 
end
 
 
else
 
else
if parameter == "yes" then
+
result = parameter == true and realization1 or ""
parameter = realization1
 
else
 
parameter = ""
 
end
 
 
end
 
end
 
else
 
else
parameter = ""
+
result = ""
add_message("<span style=\"background-color: #EEE; font-family: monospace;\">if_yes</span> needs at least one realization")
+
addMessage(monospace("ifYes") .. " needs at least one realization.")
 
end
 
end
return parameter
+
return result
 
end
 
end
  
function makeCell(output_format, a, b, c)
+
local function makeCell(outputFormat, a, b, c, format)
local cell, cell_content = "", ""
+
local cell, cellContent = "", ""
local color_CSS, other_CSS, title_attribute, sortkey, attribute_separator, converted_units_separator = "", "", "", "", "", "", ""
+
local colorCSS, otherCSS, titleAttribute, sortkey, attributeSeparator, convertedUnitsSeparator =
local style_attribute, high_low_separator, brackets, values, converted_units = {"", ""}, {"", ""}, {"", ""}, {"", ""}, {"", ""}
+
"", "", "", "", "", "", ""
 
 
if check_for_number(output_format.decimals) then
+
-- Distinguish styleAttribute variable from styleAttribute function above.
decimals = output_format.decimals
+
local styleAttribute, highLowSeparator, brackets, values, convertedUnits =
--[[ Precision is the number of decimals in the first number of the last array.
+
{"", ""}, {"", ""}, {"", ""}, {"", ""}, {"", ""}
This may be a problem for data from Weatherbase,
+
which seems to inappropriately remove .0 from numbers that have it. ]]
+
-- Precision is 1 if any number has one or more decimals.
else
+
decimals = tonumber(outputFormat.decimals) and outputFormat.decimals or format.precision
decimals = precision
 
end
 
 
 
if check_for_number(b) and check_for_number(a) then
+
if tonumber(b) and tonumber(a) then
values, high_low_separator = { round(a, decimals), round(b, decimals) }, { thinSpace .. "/" .. thinSpace, if_yes(output_format.convert_units, thinSpace .. "/" .. thinSpace) }
+
values, highLowSeparator = { round(a, decimals), round(b, decimals) },
elseif check_for_number(a) then
+
{ thinSpace .. "/" .. thinSpace, ifYes(outputFormat.convertUnits, thinSpace .. "/" .. thinSpace) }
 +
elseif tonumber(a) then
 
values = { round(a, decimals), "" }
 
values = { round(a, decimals), "" }
elseif check_for_number(c) then
+
elseif tonumber(c) then
 
values = { round(c, decimals), "" }
 
values = { round(c, decimals), "" }
 
end
 
end
if output_format.first == input_unit then
+
if output_format.convert_units == "yes" then
+
if outputFormat.first == format.inputUnit then
converted_units = { add_unit_names(convert(values[1], decimals), output_unit), add_unit_names(convert(values[2], decimals), output_unit) }
+
if outputFormat.convertUnits == true then
 +
convertedUnits = { addUnitNames(convert(values[1], format.inputUnit, decimals), outputFormat.unitNames, format.outputUnit), addUnitNames(convert(values[2], format.inputUnit, decimals), outputFormat.unitNames, format.outputUnit) }
 
end
 
end
values = { add_unit_names(values[1]), add_unit_names(values[2]) }
+
values = { addUnitNames(values[1], outputFormat.unitNames, format.inputUnit), addUnitNames(values[2], outputFormat.unitNames, format.inputUnit) }
elseif output_format.first == "C" or output_format.first == "F" then
+
elseif outputFormat.first == "C" or outputFormat.first == "F" then
if output_format.convert_units == "yes" then
+
if outputFormat.convertUnits == true then
converted_units = { add_unit_names(values[1]), add_unit_names(values[2]) }
+
convertedUnits = { addUnitNames(values[1], outputFormat.unitNames, format.inputUnit), addUnitNames(values[2], outputFormat.unitNames, format.inputUnit) }
 
end
 
end
values = { add_unit_names(convert(values[1], decimals), output_unit), add_unit_names(convert(values[2], decimals), output_unit) }
+
values = { addUnitNames(convert(values[1], format.inputUnit, decimals), outputFormat.unitNames, format.outputUnit), addUnitNames(convert(values[2], format.inputUnit, decimals), outputFormat.unitNames, format.outputUnit) }
 
else
 
else
if output_format.first == nil then
+
addMessage(monospace(tostring(outputFormat.first)) .. ", the value for " .. monospace("first") .. " in " .. monospace("outputFormat") .. " is not recognized.")
output_format.first = "nil"
 
end
 
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.")
 
 
end
 
end
 
--[[
 
--[[
Line 462: Line 447:
 
even more so when more than one of these things are true.
 
even more so when more than one of these things are true.
 
]]
 
]]
if output_format.convert_units == "yes" then
+
if outputFormat.convertUnits == true then
brackets = if_yes(output_format.brackets, "(", ")" )
+
brackets = outputFormat.brackets == true and { "(", ")" } or { "", "" }
if output_format.line_break == "auto" then
+
if outputFormat.lineBreak == "auto" then
if check_for_string(values[2]) or decimals ~= "0" or output_format.show_units == "yes" then
+
convertedUnitsSeparator = ( ine(values[2]) or decimals ~= "0" or outputFormat.showUnits == true ) and "<br>" or "&nbsp;"
converted_units_separator = "<br>"
 
else
 
converted_units_separator = "&nbsp;"
 
end
 
elseif output_format.line_break == "yes" then
 
converted_units_separator = "<br>"
 
elseif output_format.line_break == "no" then
 
converted_units_separator = "&nbsp;"
 
 
else
 
else
error("Value for line_break not recognized")
+
convertedUnitsSeparator = outputFormat.lineBreak == true and "<br>" or outputFormat.lineBreak == false and "&nbsp;" or error('Value for lineBreak not recognized')
 
end
 
end
 
end
 
end
 
 
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]
+
cellContent = values[1] .. highLowSeparator[1] .. values[2] .. convertedUnitsSeparator .. brackets[1] .. convertedUnits[1] .. highLowSeparator[2] .. convertedUnits[2] .. brackets[2]
 
 
if check_for_number(c) then
+
if tonumber(c) then
color_CSS = if_yes(output_format.color, temperature_CSS(c, input_unit, palette))
+
colorCSS = outputFormat.color == true and temperatureCSS(c, format.inputUnit, format.palette, format.inputUnit) or ""
if check_for_number(b) and check_for_number(a) then
+
if tonumber(b) and tonumber(a) then
local attribute_value
+
local attributeValue = outputFormat.first == format.inputUnit and c or convert(c, format.inputUnit, decimals)
if output_format.first == input_unit then
+
sortkey = outputFormat.sortable == true and " data-sort-value=\"" .. attributeValue .. "\"" or ""
attribute_value = c
+
titleAttribute = " title=\"Average temperature: " .. attributeValue .. " " .. degree .. outputFormat.first .. "\""
else
 
attribute_value = convert(c, decimals)
 
end
 
sortkey = if_yes(output_format.sortable, " data-sort-value=\"" .. attribute_value .. "\"")
 
title_attribute = " title=\"Average temperature: " .. attribute_value .. " " .. degree .. output_format.first .. "\""
 
 
end
 
end
elseif check_for_number(b) then
+
elseif tonumber(b) then
color_css = ""
+
colorCSS = ""
elseif check_for_number(a) then
+
elseif tonumber(a) then
color_CSS = if_yes(output_format.color, temperature_CSS(a, input_unit, palette))
+
colorCSS = outputFormat.color == true and temperatureCSS(a, format.inputUnit, format.palette) or ""
 
else
 
else
add_message("Neither a nor b nor c are strings.")
+
addMessage('Neither a nor b nor c are strings.')
 
end
 
end
other_CSS = if_yes(output_format.small_font, "font-size: 85%;")
+
otherCSS = outputFormat.smallFont == true and "font-size: 85%;" or ""
if check_for_string(color_CSS) or check_for_string(other_CSS) then
+
if ine(colorCSS) or ine(otherCSS) then
style_attribute = { "style=\"", "\"" }
+
styleAttribute = { "style=\"", "\"" }
 
end
 
end
 
 
if check_for_string(other_CSS) or check_for_string(color_CSS) or check_for_string(title_attribute) or check_for_string(sortkey) then
+
if ine(otherCSS) or ine(colorCSS) or ine(titleAttribute) or ine(sortkey) then
attribute_separator = " | "
+
attributeSeparator = " | "
 
end
 
end
cell = "\n| " .. style_attribute[1] .. color_CSS .. other_CSS .. style_attribute[2] .. title_attribute .. sortkey .. attribute_separator .. cell_content
+
cell = "\n| " .. styleAttribute[1] .. colorCSS .. otherCSS .. styleAttribute[2] .. titleAttribute .. sortkey .. attributeSeparator .. cellContent
 
return cell
 
return cell
 
end
 
end
  
function export.makeRow(frame)
+
--[[
make_arrays(frame)
+
Replaces hyphens that have a punctuation or space character before them and a number after them,
local output = ""
+
making sure that hyphens in "data-sort-type" are not replaced with minuses.
if frame.args[1] then
+
If Lua had (?<=), a capture would not be necessary.
output = "\n|-"
+
]]
output = output .. "\n! " .. frame.args[1]
+
local function hyphenToMinus(str)
if frame.args[2] then
+
return str:gsub("([%p%s])-(%d)", "%1" .. minus .. "%2")
output = output .. " !! " .. frame.args[2]
+
end
 +
 
 +
function p.makeRow(frame)
 +
local args = frame.args
 +
local format = getFormat(args.input, args.output, args.palette, args.messages)
 +
local makeArray = _makeArray(format)
 +
local a, b, c = makeArray(args.a), makeArray(args.b), makeArray(args.c)
 +
local output = {}
 +
if args[1] then
 +
table.insert(output, "\n|-")
 +
table.insert(output, "\n! " .. args[1])
 +
if args[2] then
 +
table.insert(output, " !! " .. args[2])
 
end
 
end
 
end
 
end
if cell_format then
+
if format.cellFormat then
output_format = cell_format
+
outputFormat = format.cellFormat
 
end
 
end
if a and b and c then
+
-- Assumes that if c is defined, b and a are, and if b is defined, a is.
for i = 1, length do
+
if c then
if not output_format then
+
if not outputFormat then
output_format = output_formats.high_low_average_F
+
outputFormat = outputFormats.high_low_average_F
end
+
end
output = output .. makeCell(output_format, a[i], b[i], c[i])
+
for i = 1, format.length do
 +
table.insert(output, makeCell(outputFormat, a[i], b[i], c[i], format))
 +
end
 +
elseif b then
 +
if not outputFormat then
 +
outputFormat = outputFormats.high_low_F
 
end
 
end
elseif a and b then
+
for i = 1, format.length do
for i = 1, length do
+
table.insert(output, makeCell(outputFormat, a[i], b[i], nil, format))
if not output_format then
 
output_format = output_formats.high_low_F
 
end
 
output = output .. makeCell(output_format, a[i], b[i])
 
 
end
 
end
 
elseif a then
 
elseif a then
for i = 1, length do
+
if not outputFormat then
if not output_format then
+
outputFormat = outputFormats.average_F
output_format = output_formats.average_F
+
end
end
+
for i = 1, format.length do
output = output .. makeCell(output_format, a[i])
+
table.insert(output, makeCell(outputFormat, a[i], nil, nil, format))
 
end
 
end
 
end
 
end
output = mw.ustring.gsub(output, "([%p%s])-(%d)", "%1" .. minus .. "%2")
+
output = table.concat(output)
 +
output = hyphenToMinus(output)
 
return output
 
return output
 
end
 
end
  
function export.makeTable(frame)
+
function p.makeTable(frame)
make_arrays(frame)
+
local args = frame.args
local output = "{| class=\"wikitable center nowrap\""
+
local format = getFormat(args.input, args.output, args.palette, args.messages)
if cell_format then
+
local makeArray = _makeArray(format)
output_format = cell_format
+
local a, b, c = makeArray(args.a), makeArray(args.b), makeArray(args.c)
 +
local output = { "{| class=\"wikitable center nowrap\"" }
 +
if format.cellFormat then
 +
outputFormat = format.cellFormat
 
end
 
end
if a and b and c then
+
-- Assumes that if c is defined, b and a are, and if b is defined, a is.
for i = 1, length do
+
if c then
if not output_format then
+
for i = 1, format.length do
output_format = output_formats.high_low_average_F
+
if not outputFormat then
 +
outputFormat = outputFormats.high_low_average_F
 
end
 
end
output = output .. makeCell(output_format, a[i], b[i], c[i])
+
table.insert(output, makeCell(outputFormat, a[i], b[i], c[i], format))
 
end
 
end
elseif a and b then
+
elseif b then
for i = 1, length do
+
for i = 1, format.length do
if not output_format then
+
if not outputFormat then
output_format = output_formats.high_low_F
+
outputFormat = outputFormats.high_low_F
 
end
 
end
output = output .. makeCell(output_format, a[i], b[i])
+
table.insert(output, makeCell(outputFormat, a[i], b[i], nil, format))
 
end
 
end
 
elseif a then
 
elseif a then
for i = 1, length do
+
for i = 1, format.length do
if not output_format then
+
if not outputFormat then
output_format = output_formats.average_F
+
outputFormat = outputFormats.average_F
 
end
 
end
output = output .. makeCell(output_format, a[i])
+
table.insert(output, makeCell(outputFormat, a[i], nil, nil, format))
 
end
 
end
 
end
 
end
output = mw.ustring.gsub(output, "([%p%s])-(%d)", "%1" .. minus .. "%2")
+
table.insert(output, "\n|}")
--[[  Replaces hyphens that have a punctuation or space character before them and a number after them,
+
if format.show then
making sure that hyphens in "data-sort-type" are not replaced with minuses.
+
table.insert(output, "\n\n<span style=\"color: red; font-size: 80%; line-height: 100%;\">" .. message .. "</span>")
If Lua had (?<=), a capture would not be necessary.  ]]
 
output = output .. "\n|}"
 
if show then
 
output = output .. "\n\n<span style=\"color: red; font-size: 80%; line-height: 100%;\">" .. message .. "</span>"
 
 
end
 
end
 +
output = table.concat(output)
 +
 +
output = hyphenToMinus(output)
 +
 
return output
 
return output
 
end
 
end
Line 605: Line 595:
 
]]
 
]]
  
function export.show(frame)
+
function p.show(frame)
 
-- For testing, return wikitext to show graphs of how the red/green/blue colors
 
-- For testing, return wikitext to show graphs of how the red/green/blue colors
 
-- vary with temperature, and a table of the resulting colors.
 
-- vary with temperature, and a table of the resulting colors.
Line 613: Line 603:
 
n = 0,
 
n = 0,
 
add = function (self, item)
 
add = function (self, item)
self.n = self.n + 1
+
if item then
self[self.n] = item
+
self.n = self.n + 1
 +
self[self.n] = item
 +
end
 
end,
 
end,
 
join = function (self, sep)
 
join = function (self, sep)
Line 638: Line 630:
 
local first = args[1] or -90
 
local first = args[1] or -90
 
local last = args[2] or 59
 
local last = args[2] or 59
local palette = palettes[args.palette] or palettes.cool
+
local palette = p.palettes[args.palette] or p.palettes.cool
 
local xvals, reds, greens, blues = collection(), collection(), collection(), collection()
 
local xvals, reds, greens, blues = collection(), collection(), collection(), collection()
 
local wikitext = collection()
 
local wikitext = collection()
Line 644: Line 636:
 
local columns = 0
 
local columns = 0
 
for celsius = first, last do
 
for celsius = first, last do
local background_rgb = {}
+
local backgroundRGB = {}
local style = style_attribute(palette, celsius, background_rgb)
+
local style = styleAttribute(palette, celsius, backgroundRGB)
local R = math.floor(background_rgb[1])
+
local R = math.floor(backgroundRGB[1])
local G = math.floor(background_rgb[2])
+
local G = math.floor(backgroundRGB[2])
local B = math.floor(background_rgb[3])
+
local B = math.floor(backgroundRGB[3])
 
xvals:add(celsius)
 
xvals:add(celsius)
 
reds:add(R)
 
reds:add(R)
Line 667: Line 659:
 
end
 
end
  
return export
+
return p

Latest revision as of 05:13, 30 September 2020

Testcases

Random examples

|output=nocolor sortable one line round CF
−4 / -14 (25 / 6) −2 / -12 (28 / 10) 2 / -7 (36 / 19) 8 / -1 (47 / 30) 13 / 3 (56 / 38) 18 / 7 (64 / 44) 22 / 11 (71 / 52) 22 / 12 (72 / 54) 18 / 8 (64 / 47) 11 / 2 (52 / 36) 4 / -4 (39 / 25) 0 / -11 (32 / 12) 9 / -1 (49 / 31)
|output=FC line break round units
16 °F
(−9 °C)
19 °F
(−7 °C)
28 °F
(−2 °C)
39 °F
(4 °C)
47 °F
(8 °C)
54 °F
(12 °C)
62 °F
(17 °C)
63 °F
(17 °C)
56 °F
(13 °C)
44 °F
(7 °C)
32 °F
(0 °C)
20 °F
(−7 °C)
40 °F
(4 °C)
|output=F
15.6 19.1 27.7 38.8 47.0 53.8 61.5 63.2 55.8 44.2 32.3 20.0 40.0
|output=nocolor sortable round F
25 / 6 28 / 10 36 / 19 47 / 30 56 / 38 64 / 44 71 / 52 72 / 54 64 / 47 52 / 36 39 / 25 32 / 12 49 / 31
|output=sortable C units
−4.1 °C / -14.2 °C −2.0 °C / -12.3 °C 2.4 °C / -7.2 °C 8.6 °C / -1.1 °C 13.3 °C / 3.3 °C 17.6 °C / 6.7 °C 21.8 °C / 10.9 °C 22.3 °C / 12.4 °C 17.9 °C / 8.6 °C 11.2 °C / 2.4 °C 4.1 °C / -3.8 °C −2.2 °C / -11.1 °C 9.3 °C / -0.4 °C
|a=, |b=, |c=
25 / 6
(−4 / -14)
28 / 10
(−2 / -12)
36 / 19
(2 / -7)
47 / 30
(8 / -1)
56 / 38
(13 / 3)
64 / 44
(18 / 7)
71 / 52
(22 / 11)
72 / 54
(22 / 12)
64 / 47
(18 / 8)
52 / 36
(11 / 2)
39 / 25
(4 / -4)
28 / 12
(−2 / -11)
40 (4)
|a=, |b=
24.7 / 6.5
(−4.1 / -14.2)
28.4 / 9.9
(−2.0 / -12.3)
36.3 / 19.0
(2.4 / -7.2)
47.4 / 30.1
(8.6 / -1.1)
56.0 / 37.9
(13.3 / 3.3)
63.6 / 44.0
(17.6 / 6.7)
71.2 / 51.7
(21.8 / 10.9)
72.1 / 54.3
(22.3 / 12.4)
64.3 / 47.4
(17.9 / 8.6)
52.1 / 36.4
(11.2 / 2.4)
39.3 / 25.2
(4.1 / -3.8)
28.0 / 12.0
(−2.2 / -11.1)
48.7 / 31.3
(9.3 / -0.4)
|messages=show
24.7 / 6.5
(−4.1 / -14.2)
28.4 / 9.9
(−2.0 / -12.3)
36.3 / 19.0
(2.4 / -7.2)
47.4 / 30.1
(8.6 / -1.1)
56.0 / 37.9
(13.3 / 3.3)
63.6 / 44.0
(17.6 / 6.7)
71.2 / 51.7
(21.8 / 10.9)
72.1 / 54.3
(22.3 / 12.4)
64.3 / 47.4
(17.9 / 8.6)
52.1 / 36.4
(11.2 / 2.4)
39.3 / 25.2
(4.1 / -3.8)
28.0 / 12.0
(−2.2 / -11.1)
48.7 / 31.3
(9.3 / -0.4)

Notices: No output format has been provided in the output parameter, so default values will be used.

California

Average daily high and low temperatures in °F (°C)
for locations in California,
colored and sortable by average monthly temperature
Place Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec Year
Alturas[1] 44 / 18
(7 / -8)
48 / 21
(9 / -6)
54 / 25
(12 / -4)
60 / 28
(16 / -2)
70 / 34
(21 / 1)
79 / 40
(26 / 4)
90 / 44
(32 / 7)
89 / 42
(32 / 6)
80 / 36
(27 / 2)
68 / 28
(20 / -2)
52 / 22
(11 / -6)
42 / 18
(6 / -8)
47
(8)
Bakersfield[2] 56 / 39
(13 / 4)
63 / 42
(17 / 6)
69 / 46
(21 / 8)
75 / 50
(24 / 10)
84 / 58
(29 / 14)
91 / 64
(33 / 18)
97 / 70
(36 / 21)
96 / 69
(36 / 21)
90 / 64
(32 / 18)
79 / 55
(26 / 13)
66 / 45
(19 / 7)
57 / 39
(14 / 4)
65
(18)
Bishop[3] 54 / 23
(12 / -5)
58 / 26
(14 / -3)
66 / 31
(19 / -1)
73 / 36
(23 / 2)
82 / 44
(28 / 7)
92 / 51
(33 / 11)
98 / 56
(37 / 13)
96 / 54
(36 / 12)
88 / 47
(31 / 8)
76 / 37
(24 / 3)
63 / 28
(17 / -2)
53 / 22
(12 / -6)
57
(14)
Bodie[4] 40 / 5
(4 / -15)
41 / 7
(5 / -14)
45 / 11
(7 / -12)
51 / 17
(11 / -8)
61 / 24
(16 / -4)
70 / 30
(21 / -1)
78 / 34
(26 / 1)
77 / 32
(25 / 0)
71 / 26
(22 / -3)
60 / 18
(16 / -8)
49 / 11
(9 / -12)
41 / 6
(5 / -14)
38
(3)
Death Valley[5] 67 / 40
(19 / 4)
73 / 46
(23 / 8)
82 / 55
(28 / 13)
90 / 62
(32 / 17)
100 / 73
(38 / 23)
110 / 81
(43 / 27)
116 / 88
(47 / 31)
115 / 86
(46 / 30)
106 / 76
(41 / 24)
93 / 62
(34 / 17)
77 / 48
(25 / 9)
65 / 38
(18 / 3)
77
(25)
Eureka[6] 56 / 41
(13 / 5)
56 / 42
(13 / 6)
57 / 43
(14 / 6)
58 / 44
(14 / 7)
60 / 48
(16 / 9)
62 / 50
(17 / 10)
63 / 52
(17 / 11)
64 / 53
(18 / 12)
64 / 50
(18 / 10)
62 / 47
(17 / 8)
58 / 44
(14 / 7)
55 / 41
(13 / 5)
53
(12)
Fresno[7] 55 / 38
(13 / 3)
62 / 42
(17 / 6)
68 / 46
(20 / 8)
75 / 49
(24 / 9)
84 / 56
(29 / 13)
92 / 62
(33 / 17)
98 / 68
(37 / 20)
97 / 66
(36 / 19)
91 / 62
(33 / 17)
80 / 53
(27 / 12)
65 / 43
(18 / 6)
55 / 38
(13 / 3)
64
(18)
Los Angeles[8] 68 / 48
(20 / 9)
69 / 49
(21 / 9)
70 / 51
(21 / 11)
73 / 54
(23 / 12)
74 / 57
(23 / 14)
78 / 60
(26 / 16)
83 / 64
(28 / 18)
84 / 64
(29 / 18)
83 / 63
(28 / 17)
78 / 59
(26 / 15)
73 / 52
(23 / 11)
68 / 48
(20 / 9)
65
(18)
Needles[9] 65 / 44
(18 / 7)
70 / 47
(21 / 8)
77 / 52
(25 / 11)
85 / 59
(29 / 15)
95 / 68
(35 / 20)
104 / 77
(40 / 25)
109 / 84
(43 / 29)
107 / 83
(42 / 28)
100 / 74
(38 / 23)
88 / 62
(31 / 17)
73 / 50
(23 / 10)
63 / 42
(17 / 6)
74
(23)
Redding[10] 55 / 36
(13 / 2)
60 / 39
(16 / 4)
64 / 43
(18 / 6)
70 / 46
(21 / 8)
81 / 54
(27 / 12)
90 / 62
(32 / 17)
98 / 66
(37 / 19)
97 / 63
(36 / 17)
90 / 58
(32 / 14)
78 / 49
(26 / 9)
62 / 41
(17 / 5)
54 / 36
(12 / 2)
62
(17)
Riverside[11] 69 / 43
(21 / 6)
70 / 45
(21 / 7)
73 / 46
(23 / 8)
78 / 50
(26 / 10)
82 / 55
(28 / 13)
89 / 59
(32 / 15)
95 / 64
(35 / 18)
96 / 64
(36 / 18)
92 / 61
(33 / 16)
83 / 55
(28 / 13)
75 / 46
(24 / 8)
68 / 42
(20 / 6)
67
(19)
Sacramento[12] 54 / 39
(12 / 4)
60 / 41
(16 / 5)
65 / 44
(18 / 7)
71 / 46
(22 / 8)
80 / 51
(27 / 11)
87 / 56
(31 / 13)
92 / 58
(33 / 14)
91 / 58
(33 / 14)
87 / 56
(31 / 13)
78 / 50
(26 / 10)
64 / 43
(18 / 6)
54 / 38
(12 / 3)
61
(16)
San Diego[13] 65 / 49
(18 / 9)
65 / 51
(18 / 11)
66 / 53
(19 / 12)
68 / 56
(20 / 13)
68 / 59
(20 / 15)
71 / 62
(22 / 17)
75 / 65
(24 / 18)
76 / 67
(24 / 19)
76 / 65
(24 / 18)
73 / 61
(23 / 16)
69 / 54
(21 / 12)
65 / 48
(18 / 9)
64
(18)
San
Francisco
[14]
57 / 46
(14 / 8)
60 / 48
(16 / 9)
62 / 48
(17 / 9)
63 / 49
(17 / 9)
64 / 51
(18 / 11)
66 / 53
(19 / 12)
66 / 54
(19 / 12)
68 / 55
(20 / 13)
70 / 55
(21 / 13)
69 / 54
(21 / 12)
63 / 50
(17 / 10)
57 / 46
(14 / 8)
57
(14)
San Jose[15] 58 / 42
(14 / 6)
62 / 45
(17 / 7)
66 / 47
(19 / 8)
69 / 49
(21 / 9)
74 / 52
(23 / 11)
79 / 56
(26 / 13)
82 / 58
(28 / 14)
82 / 58
(28 / 14)
80 / 57
(27 / 14)
74 / 52
(23 / 11)
64 / 46
(18 / 8)
58 / 42
(14 / 6)
61
(16)
Santa Rosa[16] 59 / 39
(15 / 4)
63 / 41
(17 / 5)
67 / 43
(19 / 6)
70 / 45
(21 / 7)
74 / 48
(23 / 9)
80 / 52
(27 / 11)
82 / 52
(28 / 11)
83 / 53
(28 / 12)
83 / 52
(28 / 11)
78 / 48
(26 / 9)
67 / 43
(19 / 6)
59 / 39
(15 / 4)
59
(15)
South Lake
Tahoe
[17]
43 / 16
(6 / -9)
44 / 18
(7 / -8)
48 / 22
(9 / -6)
54 / 27
(12 / -3)
64 / 32
(18 / 0)
72 / 37
(22 / 3)
81 / 41
(27 / 5)
80 / 40
(27 / 4)
74 / 34
(23 / 1)
63 / 28
(17 / -2)
50 / 22
(10 / -6)
43 / 16
(6 / -9)
44
(7)

Minnesota

Average Temperatures in Minnesota in °Fahrenheit (°Celsius), 1971–2000
Jan Feb Mar Apr May Jun Jul Aug Sept Oct Nov Dec Annual
Alexandria[18] 8 (−13) 15 (−9) 27 (−3) 43 (6) 56 (13) 65 (18) 70 (21) 68 (20) 58 (14) 45 (7) 28 (−2) 14 (−10) 41 (5)
Brainerd[19] 6 (−14) 13 (−11) 26 (−3) 42 (6) 56 (13) 64 (18) 69 (21) 66 (19) 56 (13) 44 (7) 28 (−2) 13 (−11) 40 (4)
Duluth[20] 10 (−12) 17 (−8) 26 (−3) 39 (4) 48 (9) 58 (14) 66 (19) 65 (18) 56 (13) 45 (7) 31 (−1) 17 (−8) 40 (4)
Grand Marais[21] 14 (−10) 19 (−7) 28 (−2) 38 (3) 47 (8) 53 (12) 61 (16) 63 (17) 55 (13) 45 (7) 32 (0) 19 (−7) 39 (4)
International Falls[22] 3 (−16) 11 (−12) 24 (−4) 39 (4) 53 (12) 62 (17) 66 (19) 64 (18) 53 (12) 42 (6) 24 (−4) 9 (−13) 32 (0)
Redwood Falls[23] 13 (−11) 20 (−7) 32 (0) 47 (8) 60 (16) 70 (21) 74 (23) 71 (22) 62 (17) 49 (9) 32 (0) 18 (−8) 46 (8)
Thief River Falls[24] 3 (−16) 11 (−12) 24 (−4) 42 (6) 56 (13) 64 (18) 69 (21) 67 (19) 56 (13) 44 (7) 24 (−4) 9 (−13) 39 (4)
Twin Cities[25] 13 (−11) 20 (−7) 32 (0) 47 (8) 59 (15) 68 (20) 73 (23) 71 (22) 61 (16) 49 (9) 32 (0) 19 (−7) 45 (7)
Winona[26] 18 (−8) 24 (−4) 36 (2) 50 (10) 62 (17) 71 (22) 76 (24) 73 (23) 64 (18) 52 (11) 32 (0) 23 (−5) 49 (9)
Worthington[27] 11 (−12) 18 (−8) 29 (−2) 44 (7) 57 (14) 67 (19) 71 (22) 68 (20) 59 (15) 47 (8) 30 (−1) 17 (−8) 43 (6)

Show function

−90 −89 −88 −87 −86 −85 −84 −83 −82 −81
−80 −79 −78 −77 −76 −75 −74 −73 −72 −71
−70 −69 −68 −67 −66 −65 −64 −63 −62 −61
−60 −59 −58 −57 −56 −55 −54 −53 −52 −51
−50 −49 −48 −47 −46 −45 −44 −43 −42 −41
−40 −39 −38 −37 −36 −35 −34 −33 −32 −31
−30 −29 −28 −27 −26 −25 −24 −23 −22 −21
−20 −19 −18 −17 −16 −15 −14 −13 −12 −11
−10 −9 −8 −7 −6 −5 −4 −3 −2 −1
0 1 2 3 4 5 6 7 8 9
10 11 12 13 14 15 16 17 18 19
20 21 22 23 24 25 26 27 28 29
30 31 32 33 34 35 36 37 38 39
40 41 42 43 44 45 46 47 48 49
50 51 52 53 54 55 56 57 58 59

<graph>{"legends":[],"scales":[{"type":"linear","name":"x","zero":false,"domain":{"data":"chart","field":"x"},"range":"width","nice":true},{"type":"linear","name":"y","domain":{"data":"chart","field":"y"},"zero":false,"range":"height","nice":true},{"domain":{"data":"chart","field":"series"},"type":"ordinal","name":"color","range":["red"]}],"version":2,"marks":[{"type":"line","properties":{"hover":{"stroke":{"value":"red"}},"update":{"stroke":{"scale":"color","field":"series"}},"enter":{"y":{"scale":"y","field":"y"},"x":{"scale":"x","field":"x"},"stroke":{"scale":"color","field":"series"},"strokeWidth":{"value":2.5}}},"from":{"data":"chart"}}],"height":180,"axes":[{"type":"x","title":"Celsius","scale":"x","format":"d","properties":{"title":{"fill":{"value":"#54595d"}},"grid":{"stroke":{"value":"#54595d"}},"ticks":{"stroke":{"value":"#54595d"}},"axis":{"strokeWidth":{"value":2},"stroke":{"value":"#54595d"}},"labels":{"fill":{"value":"#54595d"}}},"grid":false},{"type":"y","title":"Red","scale":"y","format":"d","properties":{"title":{"fill":{"value":"#54595d"}},"grid":{"stroke":{"value":"#54595d"}},"ticks":{"stroke":{"value":"#54595d"}},"axis":{"strokeWidth":{"value":2},"stroke":{"value":"#54595d"}},"labels":{"fill":{"value":"#54595d"}}},"grid":false}],"data":[{"format":{"parse":{"y":"integer","x":"integer"},"type":"json"},"name":"chart","values":[{"y":0,"series":"y","x":-90},{"y":0,"series":"y","x":-89},{"y":0,"series":"y","x":-88},{"y":0,"series":"y","x":-87},{"y":0,"series":"y","x":-86},{"y":0,"series":"y","x":-85},{"y":0,"series":"y","x":-84},{"y":0,"series":"y","x":-83},{"y":0,"series":"y","x":-82},{"y":0,"series":"y","x":-81},{"y":0,"series":"y","x":-80},{"y":0,"series":"y","x":-79},{"y":0,"series":"y","x":-78},{"y":0,"series":"y","x":-77},{"y":0,"series":"y","x":-76},{"y":0,"series":"y","x":-75},{"y":0,"series":"y","x":-74},{"y":0,"series":"y","x":-73},{"y":0,"series":"y","x":-72},{"y":0,"series":"y","x":-71},{"y":0,"series":"y","x":-70},{"y":0,"series":"y","x":-69},{"y":0,"series":"y","x":-68},{"y":0,"series":"y","x":-67},{"y":0,"series":"y","x":-66},{"y":0,"series":"y","x":-65},{"y":0,"series":"y","x":-64},{"y":0,"series":"y","x":-63},{"y":0,"series":"y","x":-62},{"y":0,"series":"y","x":-61},{"y":0,"series":"y","x":-60},{"y":0,"series":"y","x":-59},{"y":0,"series":"y","x":-58},{"y":0,"series":"y","x":-57},{"y":0,"series":"y","x":-56},{"y":0,"series":"y","x":-55},{"y":0,"series":"y","x":-54},{"y":0,"series":"y","x":-53},{"y":0,"series":"y","x":-52},{"y":0,"series":"y","x":-51},{"y":0,"series":"y","x":-50},{"y":0,"series":"y","x":-49},{"y":0,"series":"y","x":-48},{"y":0,"series":"y","x":-47},{"y":0,"series":"y","x":-46},{"y":0,"series":"y","x":-45},{"y":0,"series":"y","x":-44},{"y":0,"series":"y","x":-43},{"y":4,"series":"y","x":-42},{"y":9,"series":"y","x":-41},{"y":14,"series":"y","x":-40},{"y":20,"series":"y","x":-39},{"y":25,"series":"y","x":-38},{"y":31,"series":"y","x":-37},{"y":36,"series":"y","x":-36},{"y":41,"series":"y","x":-35},{"y":47,"series":"y","x":-34},{"y":52,"series":"y","x":-33},{"y":58,"series":"y","x":-32},{"y":63,"series":"y","x":-31},{"y":68,"series":"y","x":-30},{"y":74,"series":"y","x":-29},{"y":79,"series":"y","x":-28},{"y":85,"series":"y","x":-27},{"y":90,"series":"y","x":-26},{"y":95,"series":"y","x":-25},{"y":101,"series":"y","x":-24},{"y":106,"series":"y","x":-23},{"y":112,"series":"y","x":-22},{"y":117,"series":"y","x":-21},{"y":122,"series":"y","x":-20},{"y":128,"series":"y","x":-19},{"y":133,"series":"y","x":-18},{"y":139,"series":"y","x":-17},{"y":144,"series":"y","x":-16},{"y":149,"series":"y","x":-15},{"y":155,"series":"y","x":-14},{"y":160,"series":"y","x":-13},{"y":166,"series":"y","x":-12},{"y":171,"series":"y","x":-11},{"y":176,"series":"y","x":-10},{"y":182,"series":"y","x":-9},{"y":187,"series":"y","x":-8},{"y":193,"series":"y","x":-7},{"y":198,"series":"y","x":-6},{"y":203,"series":"y","x":-5},{"y":209,"series":"y","x":-4},{"y":214,"series":"y","x":-3},{"y":220,"series":"y","x":-2},{"y":225,"series":"y","x":-1},{"y":230,"series":"y","x":0},{"y":236,"series":"y","x":1},{"y":241,"series":"y","x":2},{"y":247,"series":"y","x":3},{"y":252,"series":"y","x":4},{"y":255,"series":"y","x":5},{"y":255,"series":"y","x":6},{"y":255,"series":"y","x":7},{"y":255,"series":"y","x":8},{"y":255,"series":"y","x":9},{"y":255,"series":"y","x":10},{"y":255,"series":"y","x":11},{"y":255,"series":"y","x":12},{"y":255,"series":"y","x":13},{"y":255,"series":"y","x":14},{"y":255,"series":"y","x":15},{"y":255,"series":"y","x":16},{"y":255,"series":"y","x":17},{"y":255,"series":"y","x":18},{"y":255,"series":"y","x":19},{"y":255,"series":"y","x":20},{"y":255,"series":"y","x":21},{"y":255,"series":"y","x":22},{"y":255,"series":"y","x":23},{"y":255,"series":"y","x":24},{"y":255,"series":"y","x":25},{"y":255,"series":"y","x":26},{"y":255,"series":"y","x":27},{"y":255,"series":"y","x":28},{"y":255,"series":"y","x":29},{"y":255,"series":"y","x":30},{"y":255,"series":"y","x":31},{"y":255,"series":"y","x":32},{"y":255,"series":"y","x":33},{"y":255,"series":"y","x":34},{"y":255,"series":"y","x":35},{"y":255,"series":"y","x":36},{"y":255,"series":"y","x":37},{"y":255,"series":"y","x":38},{"y":255,"series":"y","x":39},{"y":255,"series":"y","x":40},{"y":255,"series":"y","x":41},{"y":248,"series":"y","x":42},{"y":234,"series":"y","x":43},{"y":220,"series":"y","x":44},{"y":206,"series":"y","x":45},{"y":192,"series":"y","x":46},{"y":179,"series":"y","x":47},{"y":165,"series":"y","x":48},{"y":151,"series":"y","x":49},{"y":137,"series":"y","x":50},{"y":124,"series":"y","x":51},{"y":110,"series":"y","x":52},{"y":96,"series":"y","x":53},{"y":82,"series":"y","x":54},{"y":68,"series":"y","x":55},{"y":55,"series":"y","x":56},{"y":41,"series":"y","x":57},{"y":27,"series":"y","x":58},{"y":13,"series":"y","x":59}]}],"width":600}</graph>

<graph>{"legends":[],"scales":[{"type":"linear","name":"x","zero":false,"domain":{"data":"chart","field":"x"},"range":"width","nice":true},{"type":"linear","name":"y","domain":{"data":"chart","field":"y"},"zero":false,"range":"height","nice":true},{"domain":{"data":"chart","field":"series"},"type":"ordinal","name":"color","range":["green"]}],"version":2,"marks":[{"type":"line","properties":{"hover":{"stroke":{"value":"red"}},"update":{"stroke":{"scale":"color","field":"series"}},"enter":{"y":{"scale":"y","field":"y"},"x":{"scale":"x","field":"x"},"stroke":{"scale":"color","field":"series"},"strokeWidth":{"value":2.5}}},"from":{"data":"chart"}}],"height":180,"axes":[{"type":"x","title":"Celsius","scale":"x","format":"d","properties":{"title":{"fill":{"value":"#54595d"}},"grid":{"stroke":{"value":"#54595d"}},"ticks":{"stroke":{"value":"#54595d"}},"axis":{"strokeWidth":{"value":2},"stroke":{"value":"#54595d"}},"labels":{"fill":{"value":"#54595d"}}},"grid":false},{"type":"y","title":"Green","scale":"y","format":"d","properties":{"title":{"fill":{"value":"#54595d"}},"grid":{"stroke":{"value":"#54595d"}},"ticks":{"stroke":{"value":"#54595d"}},"axis":{"strokeWidth":{"value":2},"stroke":{"value":"#54595d"}},"labels":{"fill":{"value":"#54595d"}}},"grid":false}],"data":[{"format":{"parse":{"y":"integer","x":"integer"},"type":"json"},"name":"chart","values":[{"y":0,"series":"y","x":-90},{"y":0,"series":"y","x":-89},{"y":0,"series":"y","x":-88},{"y":0,"series":"y","x":-87},{"y":0,"series":"y","x":-86},{"y":0,"series":"y","x":-85},{"y":0,"series":"y","x":-84},{"y":0,"series":"y","x":-83},{"y":0,"series":"y","x":-82},{"y":0,"series":"y","x":-81},{"y":0,"series":"y","x":-80},{"y":0,"series":"y","x":-79},{"y":0,"series":"y","x":-78},{"y":0,"series":"y","x":-77},{"y":0,"series":"y","x":-76},{"y":0,"series":"y","x":-75},{"y":0,"series":"y","x":-74},{"y":0,"series":"y","x":-73},{"y":0,"series":"y","x":-72},{"y":0,"series":"y","x":-71},{"y":0,"series":"y","x":-70},{"y":0,"series":"y","x":-69},{"y":0,"series":"y","x":-68},{"y":0,"series":"y","x":-67},{"y":0,"series":"y","x":-66},{"y":0,"series":"y","x":-65},{"y":0,"series":"y","x":-64},{"y":0,"series":"y","x":-63},{"y":0,"series":"y","x":-62},{"y":0,"series":"y","x":-61},{"y":0,"series":"y","x":-60},{"y":0,"series":"y","x":-59},{"y":0,"series":"y","x":-58},{"y":0,"series":"y","x":-57},{"y":0,"series":"y","x":-56},{"y":0,"series":"y","x":-55},{"y":0,"series":"y","x":-54},{"y":0,"series":"y","x":-53},{"y":0,"series":"y","x":-52},{"y":0,"series":"y","x":-51},{"y":0,"series":"y","x":-50},{"y":0,"series":"y","x":-49},{"y":0,"series":"y","x":-48},{"y":0,"series":"y","x":-47},{"y":0,"series":"y","x":-46},{"y":0,"series":"y","x":-45},{"y":0,"series":"y","x":-44},{"y":0,"series":"y","x":-43},{"y":4,"series":"y","x":-42},{"y":9,"series":"y","x":-41},{"y":14,"series":"y","x":-40},{"y":20,"series":"y","x":-39},{"y":25,"series":"y","x":-38},{"y":31,"series":"y","x":-37},{"y":36,"series":"y","x":-36},{"y":41,"series":"y","x":-35},{"y":47,"series":"y","x":-34},{"y":52,"series":"y","x":-33},{"y":58,"series":"y","x":-32},{"y":63,"series":"y","x":-31},{"y":68,"series":"y","x":-30},{"y":74,"series":"y","x":-29},{"y":79,"series":"y","x":-28},{"y":85,"series":"y","x":-27},{"y":90,"series":"y","x":-26},{"y":95,"series":"y","x":-25},{"y":101,"series":"y","x":-24},{"y":106,"series":"y","x":-23},{"y":112,"series":"y","x":-22},{"y":117,"series":"y","x":-21},{"y":122,"series":"y","x":-20},{"y":128,"series":"y","x":-19},{"y":133,"series":"y","x":-18},{"y":139,"series":"y","x":-17},{"y":144,"series":"y","x":-16},{"y":149,"series":"y","x":-15},{"y":155,"series":"y","x":-14},{"y":160,"series":"y","x":-13},{"y":166,"series":"y","x":-12},{"y":171,"series":"y","x":-11},{"y":176,"series":"y","x":-10},{"y":182,"series":"y","x":-9},{"y":187,"series":"y","x":-8},{"y":193,"series":"y","x":-7},{"y":198,"series":"y","x":-6},{"y":203,"series":"y","x":-5},{"y":209,"series":"y","x":-4},{"y":214,"series":"y","x":-3},{"y":220,"series":"y","x":-2},{"y":225,"series":"y","x":-1},{"y":230,"series":"y","x":0},{"y":236,"series":"y","x":1},{"y":241,"series":"y","x":2},{"y":247,"series":"y","x":3},{"y":252,"series":"y","x":4},{"y":251,"series":"y","x":5},{"y":244,"series":"y","x":6},{"y":237,"series":"y","x":7},{"y":230,"series":"y","x":8},{"y":223,"series":"y","x":9},{"y":217,"series":"y","x":10},{"y":210,"series":"y","x":11},{"y":203,"series":"y","x":12},{"y":196,"series":"y","x":13},{"y":189,"series":"y","x":14},{"y":182,"series":"y","x":15},{"y":175,"series":"y","x":16},{"y":168,"series":"y","x":17},{"y":161,"series":"y","x":18},{"y":155,"series":"y","x":19},{"y":148,"series":"y","x":20},{"y":141,"series":"y","x":21},{"y":134,"series":"y","x":22},{"y":127,"series":"y","x":23},{"y":120,"series":"y","x":24},{"y":113,"series":"y","x":25},{"y":106,"series":"y","x":26},{"y":99,"series":"y","x":27},{"y":93,"series":"y","x":28},{"y":86,"series":"y","x":29},{"y":79,"series":"y","x":30},{"y":72,"series":"y","x":31},{"y":65,"series":"y","x":32},{"y":58,"series":"y","x":33},{"y":51,"series":"y","x":34},{"y":44,"series":"y","x":35},{"y":37,"series":"y","x":36},{"y":31,"series":"y","x":37},{"y":24,"series":"y","x":38},{"y":17,"series":"y","x":39},{"y":10,"series":"y","x":40},{"y":3,"series":"y","x":41},{"y":0,"series":"y","x":42},{"y":0,"series":"y","x":43},{"y":0,"series":"y","x":44},{"y":0,"series":"y","x":45},{"y":0,"series":"y","x":46},{"y":0,"series":"y","x":47},{"y":0,"series":"y","x":48},{"y":0,"series":"y","x":49},{"y":0,"series":"y","x":50},{"y":0,"series":"y","x":51},{"y":0,"series":"y","x":52},{"y":0,"series":"y","x":53},{"y":0,"series":"y","x":54},{"y":0,"series":"y","x":55},{"y":0,"series":"y","x":56},{"y":0,"series":"y","x":57},{"y":0,"series":"y","x":58},{"y":0,"series":"y","x":59}]}],"width":600}</graph>

<graph>{"legends":[],"scales":[{"type":"linear","name":"x","zero":false,"domain":{"data":"chart","field":"x"},"range":"width","nice":true},{"type":"linear","name":"y","domain":{"data":"chart","field":"y"},"zero":false,"range":"height","nice":true},{"domain":{"data":"chart","field":"series"},"type":"ordinal","name":"color","range":["blue"]}],"version":2,"marks":[{"type":"line","properties":{"hover":{"stroke":{"value":"red"}},"update":{"stroke":{"scale":"color","field":"series"}},"enter":{"y":{"scale":"y","field":"y"},"x":{"scale":"x","field":"x"},"stroke":{"scale":"color","field":"series"},"strokeWidth":{"value":2.5}}},"from":{"data":"chart"}}],"height":180,"axes":[{"type":"x","title":"Celsius","scale":"x","format":"d","properties":{"title":{"fill":{"value":"#54595d"}},"grid":{"stroke":{"value":"#54595d"}},"ticks":{"stroke":{"value":"#54595d"}},"axis":{"strokeWidth":{"value":2},"stroke":{"value":"#54595d"}},"labels":{"fill":{"value":"#54595d"}}},"grid":false},{"type":"y","title":"Blue","scale":"y","format":"d","properties":{"title":{"fill":{"value":"#54595d"}},"grid":{"stroke":{"value":"#54595d"}},"ticks":{"stroke":{"value":"#54595d"}},"axis":{"strokeWidth":{"value":2},"stroke":{"value":"#54595d"}},"labels":{"fill":{"value":"#54595d"}}},"grid":false}],"data":[{"format":{"parse":{"y":"integer","x":"integer"},"type":"json"},"name":"chart","values":[{"y":0,"series":"y","x":-90},{"y":5,"series":"y","x":-89},{"y":10,"series":"y","x":-88},{"y":16,"series":"y","x":-87},{"y":21,"series":"y","x":-86},{"y":27,"series":"y","x":-85},{"y":32,"series":"y","x":-84},{"y":37,"series":"y","x":-83},{"y":43,"series":"y","x":-82},{"y":48,"series":"y","x":-81},{"y":54,"series":"y","x":-80},{"y":59,"series":"y","x":-79},{"y":64,"series":"y","x":-78},{"y":70,"series":"y","x":-77},{"y":75,"series":"y","x":-76},{"y":81,"series":"y","x":-75},{"y":86,"series":"y","x":-74},{"y":91,"series":"y","x":-73},{"y":97,"series":"y","x":-72},{"y":102,"series":"y","x":-71},{"y":108,"series":"y","x":-70},{"y":113,"series":"y","x":-69},{"y":118,"series":"y","x":-68},{"y":124,"series":"y","x":-67},{"y":129,"series":"y","x":-66},{"y":135,"series":"y","x":-65},{"y":140,"series":"y","x":-64},{"y":145,"series":"y","x":-63},{"y":151,"series":"y","x":-62},{"y":156,"series":"y","x":-61},{"y":162,"series":"y","x":-60},{"y":167,"series":"y","x":-59},{"y":172,"series":"y","x":-58},{"y":178,"series":"y","x":-57},{"y":183,"series":"y","x":-56},{"y":189,"series":"y","x":-55},{"y":194,"series":"y","x":-54},{"y":199,"series":"y","x":-53},{"y":205,"series":"y","x":-52},{"y":210,"series":"y","x":-51},{"y":216,"series":"y","x":-50},{"y":221,"series":"y","x":-49},{"y":226,"series":"y","x":-48},{"y":232,"series":"y","x":-47},{"y":237,"series":"y","x":-46},{"y":243,"series":"y","x":-45},{"y":248,"series":"y","x":-44},{"y":253,"series":"y","x":-43},{"y":255,"series":"y","x":-42},{"y":255,"series":"y","x":-41},{"y":255,"series":"y","x":-40},{"y":255,"series":"y","x":-39},{"y":255,"series":"y","x":-38},{"y":255,"series":"y","x":-37},{"y":255,"series":"y","x":-36},{"y":255,"series":"y","x":-35},{"y":255,"series":"y","x":-34},{"y":255,"series":"y","x":-33},{"y":255,"series":"y","x":-32},{"y":255,"series":"y","x":-31},{"y":255,"series":"y","x":-30},{"y":255,"series":"y","x":-29},{"y":255,"series":"y","x":-28},{"y":255,"series":"y","x":-27},{"y":255,"series":"y","x":-26},{"y":255,"series":"y","x":-25},{"y":255,"series":"y","x":-24},{"y":255,"series":"y","x":-23},{"y":255,"series":"y","x":-22},{"y":255,"series":"y","x":-21},{"y":255,"series":"y","x":-20},{"y":255,"series":"y","x":-19},{"y":255,"series":"y","x":-18},{"y":255,"series":"y","x":-17},{"y":255,"series":"y","x":-16},{"y":255,"series":"y","x":-15},{"y":255,"series":"y","x":-14},{"y":255,"series":"y","x":-13},{"y":255,"series":"y","x":-12},{"y":255,"series":"y","x":-11},{"y":255,"series":"y","x":-10},{"y":255,"series":"y","x":-9},{"y":255,"series":"y","x":-8},{"y":255,"series":"y","x":-7},{"y":255,"series":"y","x":-6},{"y":255,"series":"y","x":-5},{"y":255,"series":"y","x":-4},{"y":255,"series":"y","x":-3},{"y":255,"series":"y","x":-2},{"y":255,"series":"y","x":-1},{"y":255,"series":"y","x":0},{"y":255,"series":"y","x":1},{"y":255,"series":"y","x":2},{"y":255,"series":"y","x":3},{"y":255,"series":"y","x":4},{"y":248,"series":"y","x":5},{"y":234,"series":"y","x":6},{"y":220,"series":"y","x":7},{"y":206,"series":"y","x":8},{"y":192,"series":"y","x":9},{"y":179,"series":"y","x":10},{"y":165,"series":"y","x":11},{"y":151,"series":"y","x":12},{"y":137,"series":"y","x":13},{"y":124,"series":"y","x":14},{"y":110,"series":"y","x":15},{"y":96,"series":"y","x":16},{"y":82,"series":"y","x":17},{"y":68,"series":"y","x":18},{"y":55,"series":"y","x":19},{"y":41,"series":"y","x":20},{"y":27,"series":"y","x":21},{"y":13,"series":"y","x":22},{"y":0,"series":"y","x":23},{"y":0,"series":"y","x":24},{"y":0,"series":"y","x":25},{"y":0,"series":"y","x":26},{"y":0,"series":"y","x":27},{"y":0,"series":"y","x":28},{"y":0,"series":"y","x":29},{"y":0,"series":"y","x":30},{"y":0,"series":"y","x":31},{"y":0,"series":"y","x":32},{"y":0,"series":"y","x":33},{"y":0,"series":"y","x":34},{"y":0,"series":"y","x":35},{"y":0,"series":"y","x":36},{"y":0,"series":"y","x":37},{"y":0,"series":"y","x":38},{"y":0,"series":"y","x":39},{"y":0,"series":"y","x":40},{"y":0,"series":"y","x":41},{"y":0,"series":"y","x":42},{"y":0,"series":"y","x":43},{"y":0,"series":"y","x":44},{"y":0,"series":"y","x":45},{"y":0,"series":"y","x":46},{"y":0,"series":"y","x":47},{"y":0,"series":"y","x":48},{"y":0,"series":"y","x":49},{"y":0,"series":"y","x":50},{"y":0,"series":"y","x":51},{"y":0,"series":"y","x":52},{"y":0,"series":"y","x":53},{"y":0,"series":"y","x":54},{"y":0,"series":"y","x":55},{"y":0,"series":"y","x":56},{"y":0,"series":"y","x":57},{"y":0,"series":"y","x":58},{"y":0,"series":"y","x":59}]}],"width":600}</graph>


  1. Template:WRCC
  2. Template:WRCC
  3. Template:WRCC
  4. Template:WRCC
  5. Template:WRCC
  6. Template:WRCC
  7. Template:WRCC
  8. Template:WRCC
  9. Template:WRCC
  10. Template:WRCC
  11. Template:WRCC
  12. Template:WRCC
  13. Template:WRCC
  14. Template:WRCC
  15. Template:WRCC
  16. Template:WRCC
  17. Template:WRCC
  18. Ross, Douglas (1971–2000). "Daily Normals for Alexandria, Minnesota". NCDC. Retrieved 2007-06-07.
  19. Ross, Douglas (1971–2000). "Daily Normals for Brainerd, Minnesota". NCDC. Retrieved 2007-12-27.
  20. Ross, Douglas (1971–2000). "Daily Normals for Duluth, Minnesota". NCDC. Retrieved 2007-06-07.
  21. Ross, Douglas (1971–2000). "Daily Normals for Grand Marais, Minnesota". NCDC. Retrieved 2007-12-27.
  22. Ross, Douglas (1971–2000). "Daily Normals for International Falls, Minnesota". NCDC. Retrieved 2007-06-07.
  23. Ross, Douglas (1971–2000). "Daily Normals for Redwood Falls, Minnesota". NCDC. Retrieved 2007-06-07.
  24. Ross, Douglas (1971–2000). "Daily Normals for Thief River Falls, Minnesota". NCDC. Retrieved 2007-06-07.
  25. Ross, Douglas (1971–2000). "Daily Normals for the Twin Cities". NCDC. Retrieved 2007-06-07.
  26. Ross, Douglas (1971–2000). "Daily Normals for Winona, Minnesota". NCDC. Retrieved 2007-06-07.
  27. Ross, Douglas (1971–2000). "Daily Normals for Worthington, Minnesota". NCDC. Retrieved 2007-06-07.
local p = {}

require('Module:No globals')

local degree = "°" -- used by addUnitNames()
local minus = "−" -- used by makeRow() and makeTable()
local thinSpace = mw.ustring.char(0x2009) -- used by makeCell()

local precision, decimals

-- if not empty
local function ine(var)
	var = tostring(var)
	if var == "" then
		return nil
	else
		return var
	end
end

-- Error message handling
local message = ""

local function addMessage(newMessage)
	if ine(message) then
		message = message .. " " .. newMessage
	else
		message = "Notices: " .. newMessage
	end
end

local function monospace(str)
	return '<span style="background-color: #EEE; font-family: monospace;">' .. str .. '</span>'
end

-- Input and output parameters
local function getFormat(inputParameter, outputParameter, palette, messages)
	local length, inputUnit, outputUnit, palette, show, cellFormat
	
	if inputParameter == nil then
		error('Please provide the number of values and a unit in the input parameter')
	else
		-- Find as many as two digits in the input parameter.
		length = tonumber(string.match(inputParameter, "(%d%d?)")) 
		if not length then
			length = 13
			addMessage('getFormat has not found a length value in the input parameter; length defaults to "13"')
		end
		
		-- Find C or F, but not both
		if string.find(inputParameter, "C") and string.find(inputParameter, "F") then
			error("Input unit must be either C (Celsius) or F (Fahrenheit)")
		else
			inputUnit = string.match(inputParameter, "([CF])") or error("Please provide an input unit in the input parameter: F for Fahrenheit or C for Celsius", 0)
		end
		
		if inputUnit == "C" then
			outputUnit = "F"
		else
			outputUnit = "C"
		end
		
		-- Make sure nothing except C, F, numbers, or spaces is in the input parameter.
		if string.find(inputParameter, "[^CF%d%s]") then
			addMessage("There are extraneous characters in the " .. monospace("output") .. " parameter.")
		end
	end
	
	if outputParameter == nil then
		-- Since there are default values, the module will still generate output with an empty output parameter.
		addMessage("No output format has been provided in the " .. monospace("output") .. " parameter, so default values will be used.")
	else
		cellFormat = {}
		for i, unit in require("Module:StringTools").imatch(outputParameter, "[CF]") do
			cellFormat[i] = unit
			if i > 2 then
				break
			end
		end
		local function setFormat(key, variable, value)
			if string.find(outputParameter, key) then
				cellFormat[variable] = value
			else
				cellFormat[variable] = not value
			end
		end
		if cellFormat[1] then
			cellFormat.first = cellFormat[1]
		else
			error('C or F not found in output parameter')
		end
		if cellFormat[2] == nil then
			cellFormat["convertUnits"] = false
		else
			if cellFormat[2] == cellFormat[1] then
				error('There should not be two of the same unit name in the output parameter.')
			else
				cellFormat["convertUnits"] = true
			end
		end
		setFormat("unit", "unitNames", true)
		setFormat("no ?color", "color", false)
		setFormat("sort", "sortable", true)
		setFormat("full ?size", "smallFont", false)
		setFormat("no ?brackets", "brackets", false)
		setFormat("round", "decimals", "0", "")
		if string.find(outputParameter, "line break") then
			cellFormat["lineBreak"] = true
		elseif string.find(outputParameter, "one line") then
			cellFormat["lineBreak"] = false
		else
			cellFormat["lineBreak"] = "auto"
		end
		if string.find(outputParameter, "one line") and
			string.find(outputParameter, "line break") then
			error('Place either "one line" or "line break" in the output parameter, not both')
		end
	end
	
	palette = palette or "cool2avg"
	
	show = messages == "show"
	
	return {
		length = length, inputUnit = inputUnit, outputUnit = outputUnit,
		cellFormat = cellFormat, show = show, palette = palette
	}
end

-- Math functions

local function round(value, decimals)
	value = tonumber(value)
	if type(value) == "number" then
		return string.format("%." .. decimals .. "f", value)
	else
		error("Format was asked to operate on " .. tostring(value) .. ", which cannot be converted to a number.", 2)
		return ""
	end
end

local function convert(value, unit, decimals) -- Unit is the unit being converted from.
	if not unit then
		error("No unit supplied to convert.", 2)
	end
	if tonumber(value) then
		local value = tonumber(value)
		if unit == "C" then
			return round(value * 9/5 + 32, decimals)
		elseif unit == "F" then
			return round((value - 32) * 5/9, decimals)
		else
			error("Input unit not recognized", 2)
		end
	else
		-- to avoid concatenation errors
		return "" 
	end
end

-- Stick numbers into array. Find out if any have decimals.
-- Throw an error if any are invalid.
local function _makeArray(format)
	return function(parameter)
		if not parameter then
			return nil
		end
		local array = {}
		-- If there are multiple parameters for numbers, and the first doesn't have
		-- decimals, the rest will have their decimals rounded off.
		format.precision = format.precision or parameter:find("%d%.%d") and "1" or "0"
		
		local numbers = mw.text.split(parameter, "%s+")
		if #numbers ~= format.length then
			addMessage('There are not ' .. format.length .. ' values in the ' .. parameter .. ' parameter.')
		end
		
		for i, number in ipairs(numbers) do
			if not number:find("^%-?%d%d?%d?.?(%d?)$") then
				error('The number "' .. number .. '" does not fit the expected pattern.')
			end
			
			table.insert(array, number)
		end
		
		return array
	end
end

-- Color generation

p.palettes = {
	--[[
		The first three arrays in each palette defines background color using a
		table of four numbers, say { 11, 22, 33, 44 } (values in °C).
		That means that, on the scale from 0 (black) to 255 (saturated), the color
		is 0 below 11°C and above 44°C, and is 255 from 22°C to 33°C.
		The color rises from 0 to 255 between 11°C and 22°C, and falls from 255 to 0
		between 33°C and 44°C.
	]]
	cool = {
		{ -42.75,   4.47, 41.5, 60   }, -- red
		{ -42.75,   4.47,  4.5, 41.5 }, -- green
		{ -90   , -42.78,  4.5, 23   }, -- blue
		white = { -23.3, 37.8 },		-- background
	},
	cool2 = {
		{ -42.75,   4.5 , 41.5, 56   },
		{ -42.75,   4.5 ,  4.5, 41.5 },
		{ -90   , -42.78,  4.5, 23   },
		white = { -23.3, 35 },
	},
	cool2avg = {
		{ -38,   4.5, 25  , 45   },
		{ -38,   4.5,  4.5, 30   },
		{ -70, -38  ,  4.5, 23   },
		white = { -23.3, 25 },
	},
}

--[[ Return style for a table cell based on the given value which
	should be a temperature in °C. ]]
local function temperatureColor(palette, value, outRGB)
	local backgroundColor, textColor
	value = tonumber(value)
	if not value then
		backgroundColor, textColor = 'FFF', '000'
		addMessage("Value supplied to " .. monospace("temperatureColor") .. " is not recognized.")
	else
		local min, max = unpack(palette.white or { -23, 35 })
		if value < min or value >= max then
			textColor = 'FFF'
			-- Else nil.
			-- This assumes that black text color is the default for most readers.
		end

		local backgroundRGB = outRGB or {}
		for i, v in ipairs(palette) do
			local a, b, c, d = unpack(v)
			if value <= a then
				backgroundRGB[i] = 0
			elseif value < b then
				backgroundRGB[i] = (value - a) * 255 / (b - a)
			elseif value <= c then
				backgroundRGB[i] = 255
			elseif value < d then
				backgroundRGB[i] = 255 - ( (value - c) * 255 / (d - c) )
			else
				backgroundRGB[i] = 0
			end
		end
		backgroundColor = string.format('%02X%02X%02X', unpack(backgroundRGB))
	end
	return backgroundColor, textColor
end

local function colorCSS(backgroundColor, textColor)
	if backgroundColor and textColor then
		return 'background: #' .. backgroundColor .. '; color: #' .. textColor .. ';'
	elseif backgroundColor then
		return 'background: #' .. backgroundColor .. ';'
	else
		return ''
	end
end

local function temperatureColorCSS(palette, value, outRGB)
	return colorCSS(temperatureColor(palette, value, outRGB))
end

local function temperatureCSS(value, unit, palette)
	local palette = p.palettes[palette] or p.palettes.cool
	local value = tonumber(value)
	if value == nil then
		error("The function " .. monospace("temperatureCSS") .. " is receiving a nil value")
	else
		if unit == 'F' then
			value = convert(value, 'F', decimals)
		elseif unit ~= 'C' then
			unitError(unit or "nil")
		end
		return colorCSS(temperatureColor(palette, value))
	end
end

local function styleAttribute(palette, value, outRGB)
	local fontSize = "font-size: 85%;"
	local color = temperatureColorCSS(palette, value, outRGB)
	return 'style=\"' .. color .. ' ' .. fontSize .. '\"'
end

local style_attribute = styleAttribute

--[=[
	Used by {{Average temperature table/row/C/sandbox}},
	{{Average temperature table/row/F/sandbox}},
	{{Average temperature table/row/C/sandbox}},
	{{Template:Avg temp row F/sandbox2}},
	{{Template:Avg temp row C/sandbox2}}.
]=]
function p.temperatureStyle(frame)
	local palette = p.palettes[frame.args.palette] or p.palettes.cool
	local unit = frame.args.unit or 'C'
	local value = tonumber(frame.args[1])
	if unit == 'F' then
		value = convert(value, 'F', 1)
	elseif unit ~= 'C' then
		error('Unrecognized unit: ' .. unit)
	end
	return styleAttribute(palette, value)
end

p.temperature_style = p.temperatureStyle

--[[ ==== Cell, row, table generation ==== ]]
local outputFormats = {
	high_low_average_F =
		{ first = "F",
		convertUnits = true,
		unitNames = false,
		color = true,
		smallFont = true,
		sortable = true,
		decimals = "0",
		brackets = true,
		lineBreak = "auto", },
	high_low_average_C =
		{ first = "C",
		convertUnits = true,
		unitNames = false,
		color = true,
		smallFont = true,
		sortable = true,
		decimals = "0",
		brackets = true,
		lineBreak = "auto", },
	high_low_F =
		{ first = "F",
		convertUnits = true,
		unitNames = false,
		color = false,
		smallFont = true,
		sortable = false,
		decimals = "",
		brackets = true,
		lineBreak = "auto", },
	high_low_C =
		{ first = "C",
		convertUnits = true,
		unitNames = false,
		color = false,
		smallFont = true,
		sortable = false,
		decimals = "0",
		brackets = true,
		lineBreak = "auto", },
	average_F =
		{ first = "F",
		convertUnits = true,
		unitNames = false,
		color = true,
		smallFont = true,
		sortable = false,
		decimals = "0",
		brackets = true,
		lineBreak = "auto", },
	average_C =
		{ first = "C",
		convertUnits = true,
		unitNames = false,
		color = true,
		smallFont = true,
		sortable = false,
		decimals = "0",
		brackets = true,
		lineBreak = "auto", },
	}

local outputFormat

local function addUnitNames(value, yesOrNo, unit)
	if not unit then
		error("No unit supplied as argument 3 to addUnitNames", 2)
	end
	-- Don't add a unit name to an empty string
	value = yesOrNo == true and ine(value) and value .. "&nbsp;" .. degree .. unit or value
	return value
end

local function ifYes(parameter, realization1, realization2)
	local result
	if realization1 then
		if realization2 then
			result = parameter == true and { realization1, realization2 } or { "", "" }
		else
			result = parameter == true and realization1 or ""
		end
	else
		result = ""
		addMessage(monospace("ifYes") .. " needs at least one realization.")
	end
	return result
end

local function makeCell(outputFormat, a, b, c, format)
	local cell, cellContent = "", ""
	local colorCSS, otherCSS, titleAttribute, sortkey, attributeSeparator, convertedUnitsSeparator =
		"", "", "", "", "", "", ""
	
	-- Distinguish styleAttribute variable from styleAttribute function above.
	local styleAttribute, highLowSeparator, brackets, values, convertedUnits =
		{"", ""}, {"", ""}, {"", ""}, {"", ""}, {"", ""}
	
	-- Precision is 1 if any number has one or more decimals.
	decimals = tonumber(outputFormat.decimals) and outputFormat.decimals or format.precision
	
	if tonumber(b) and tonumber(a) then
		values, highLowSeparator = { round(a, decimals), round(b, decimals) },
			{ thinSpace .. "/" .. thinSpace, ifYes(outputFormat.convertUnits, thinSpace .. "/" .. thinSpace) }
	elseif tonumber(a) then
		values = { round(a, decimals), "" }
	elseif tonumber(c) then
		values = { round(c, decimals), "" }
	end
	
	if outputFormat.first == format.inputUnit then
		if outputFormat.convertUnits == true then
			convertedUnits = { addUnitNames(convert(values[1], format.inputUnit, decimals), outputFormat.unitNames, format.outputUnit), addUnitNames(convert(values[2], format.inputUnit, decimals), outputFormat.unitNames, format.outputUnit) }
		end
		values = { addUnitNames(values[1], outputFormat.unitNames, format.inputUnit), addUnitNames(values[2], outputFormat.unitNames, format.inputUnit) }
	elseif outputFormat.first == "C" or outputFormat.first == "F" then
		if outputFormat.convertUnits == true then
			convertedUnits = { addUnitNames(values[1], outputFormat.unitNames, format.inputUnit), addUnitNames(values[2], outputFormat.unitNames, format.inputUnit) }
		end
		values = { addUnitNames(convert(values[1], format.inputUnit, decimals), outputFormat.unitNames, format.outputUnit), addUnitNames(convert(values[2], format.inputUnit, decimals), outputFormat.unitNames, format.outputUnit) }
	else
		addMessage(monospace(tostring(outputFormat.first)) .. ", the value for " .. monospace("first") .. " in " .. monospace("outputFormat") .. " is not recognized.")
	end
	--[[
		Regarding line breaks:
		If there are two values, there will be at least three characters: 9/1.
		If there is one decimal, numbers will be three to five characters long
		and there will be 3 to 10 characters total even without unit conversion:
			1.1, 116.5/88.0.
		If there are units, that adds three characters per number: 25 °C/20 °C.
		In each of these cases, a line break is needed so that table cells are not too wide;
		even more so when more than one of these things are true.
		]]
	if outputFormat.convertUnits == true then
		brackets = outputFormat.brackets == true and { "(", ")" } or { "", "" }
		if outputFormat.lineBreak == "auto" then
			convertedUnitsSeparator = ( ine(values[2]) or decimals ~= "0" or outputFormat.showUnits == true ) and "<br>" or "&nbsp;"
		else
			convertedUnitsSeparator = outputFormat.lineBreak == true and "<br>" or outputFormat.lineBreak == false and "&nbsp;" or error('Value for lineBreak not recognized')
		end
	end
	
	cellContent = values[1] .. highLowSeparator[1] .. values[2] .. convertedUnitsSeparator .. brackets[1] .. convertedUnits[1] .. highLowSeparator[2] .. convertedUnits[2] .. brackets[2]
	
	if tonumber(c) then
		colorCSS = outputFormat.color == true and temperatureCSS(c, format.inputUnit, format.palette, format.inputUnit) or ""
		if tonumber(b) and tonumber(a) then
			local attributeValue = outputFormat.first == format.inputUnit and c or convert(c, format.inputUnit, decimals)
			sortkey = outputFormat.sortable == true and " data-sort-value=\"" .. attributeValue .. "\"" or ""
			titleAttribute = " title=\"Average temperature: " .. attributeValue .. " " .. degree .. outputFormat.first .. "\""
		end
	elseif tonumber(b) then
		colorCSS = ""
	elseif tonumber(a) then
		colorCSS = outputFormat.color == true and temperatureCSS(a, format.inputUnit, format.palette) or ""
	else
		addMessage('Neither a nor b nor c are strings.')
	end
	otherCSS = outputFormat.smallFont == true and "font-size: 85%;" or ""
	if ine(colorCSS) or ine(otherCSS) then
		styleAttribute = { "style=\"", "\"" }
	end
	
	if ine(otherCSS) or ine(colorCSS) or ine(titleAttribute) or ine(sortkey) then
		attributeSeparator = " | "
	end
	cell = "\n| " .. styleAttribute[1] .. colorCSS .. otherCSS .. styleAttribute[2] .. titleAttribute .. sortkey .. attributeSeparator .. cellContent
	return cell
end

--[[
	Replaces hyphens that have a punctuation or space character before them and a number after them,
	making sure that hyphens in "data-sort-type" are not replaced with minuses.
	If Lua had (?<=), a capture would not be necessary. 
]]
local function hyphenToMinus(str)
	return str:gsub("([%p%s])-(%d)", "%1" .. minus .. "%2")
end

function p.makeRow(frame)
	local args = frame.args
	local format = getFormat(args.input, args.output, args.palette, args.messages)
	local makeArray = _makeArray(format)
	local a, b, c = makeArray(args.a), makeArray(args.b), makeArray(args.c)
	local output = {}
	if args[1] then
		table.insert(output, "\n|-")
		table.insert(output, "\n! " .. args[1])
		if args[2] then
			table.insert(output, " !! " .. args[2])
		end
	end
	if format.cellFormat then
		outputFormat = format.cellFormat
	end
	-- Assumes that if c is defined, b and a are, and if b is defined, a is.
	if c then
		if not outputFormat then
			outputFormat = outputFormats.high_low_average_F
		end
		for i = 1, format.length do
			table.insert(output, makeCell(outputFormat, a[i], b[i], c[i], format))
		end
	elseif b then
		if not outputFormat then
			outputFormat = outputFormats.high_low_F
		end
		for i = 1, format.length do
			table.insert(output, makeCell(outputFormat, a[i], b[i], nil, format))
		end
	elseif a then
		if not outputFormat then
			outputFormat = outputFormats.average_F
		end
		for i = 1, format.length do
			table.insert(output, makeCell(outputFormat, a[i], nil, nil, format))
		end
	end
	output = table.concat(output)
	output = hyphenToMinus(output)
	return output
end

function p.makeTable(frame)
	local args = frame.args
	local format = getFormat(args.input, args.output, args.palette, args.messages)
	local makeArray = _makeArray(format)
	local a, b, c = makeArray(args.a), makeArray(args.b), makeArray(args.c)
	local output = { "{| class=\"wikitable center nowrap\"" }
	if format.cellFormat then
		outputFormat = format.cellFormat
	end
	-- Assumes that if c is defined, b and a are, and if b is defined, a is.
	if c then
		for i = 1, format.length do
			if not outputFormat then
				outputFormat = outputFormats.high_low_average_F
			end
			table.insert(output, makeCell(outputFormat, a[i], b[i], c[i], format))
		end
	elseif b then
		for i = 1, format.length do
			if not outputFormat then
				outputFormat = outputFormats.high_low_F
			end
			table.insert(output, makeCell(outputFormat, a[i], b[i], nil, format))
		end
	elseif a then
		for i = 1, format.length do
			if not outputFormat then
				outputFormat = outputFormats.average_F
			end
			table.insert(output, makeCell(outputFormat, a[i], nil, nil, format))
		end
	end
	table.insert(output, "\n|}")
	if format.show then
		table.insert(output, "\n\n<span style=\"color: red; font-size: 80%; line-height: 100%;\">" .. message .. "</span>")
	end
	output = table.concat(output)
	
	output = hyphenToMinus(output)
	
	return output
end



local chart = [[
{{Graph:Chart
|width=600
|height=180
|xAxisTitle=Celsius
|yAxisTitle=__COLOR
|type=line
|x=__XVALUES
|y=__YVALUES
|colors=__COLOR
}}
]]

function p.show(frame)
	-- For testing, return wikitext to show graphs of how the red/green/blue colors
	-- vary with temperature, and a table of the resulting colors.
	local function collection()
		-- Return a table to hold items.
		return {
			n = 0,
			add = function (self, item)
				if item then
					self.n = self.n + 1
					self[self.n] = item
				end
			end,
			join = function (self, sep)
				return table.concat(self, sep)
			end,
		}
	end
	local function make_chart(result, color, xvalues, yvalues)
		result:add('\n')
		result:add(frame:preprocess((chart:gsub('__[A-Z]+', {
			__COLOR = color,
			__XVALUES = xvalues:join(','),
			__YVALUES = yvalues:join(','),
		}))))
	end
	local function with_minus(value)
		if value < 0 then
			return minus .. tostring(-value)
		end
		return tostring(value)
	end
	local args = frame.args
	local first = args[1] or -90
	local last = args[2] or 59
	local palette = p.palettes[args.palette] or p.palettes.cool
	local xvals, reds, greens, blues = collection(), collection(), collection(), collection()
	local wikitext = collection()
	wikitext:add('{| class="wikitable"\n|-\n')
	local columns = 0
	for celsius = first, last do
		local backgroundRGB = {}
		local style = styleAttribute(palette, celsius, backgroundRGB)
		local R = math.floor(backgroundRGB[1])
		local G = math.floor(backgroundRGB[2])
		local B = math.floor(backgroundRGB[3])
		xvals:add(celsius)
		reds:add(R)
		greens:add(G)
		blues:add(B)
		wikitext:add('| ' .. style .. ' | ' .. with_minus(celsius) .. '\n')
		columns = columns + 1
		if columns >= 10 then
			columns = 0
			wikitext:add('|-\n')
		end
	end
	wikitext:add('|}\n')
	make_chart(wikitext, 'Red', xvals, reds)
	make_chart(wikitext, 'Green', xvals, greens)
	make_chart(wikitext, 'Blue', xvals, blues)
	return wikitext:join()
end

return p