Jump to content

Module:Storm categories: Difference between revisions

From Wikipedia, the free encyclopedia
Content deleted Content added
add tropical cyclone status icons
adjust aliases
Line 40: Line 40:
name = "Tropical depression",
name = "Tropical depression",
sortkey = 90,
sortkey = 90,
icon = "[[File:Tropical_Depression.png|25px]]"
icon = "[[File:Tropical Depression.png|25px]]"
},
},
disturbance = {
disturbance = {
Line 68: Line 68:
color = "80ccff",
color = "80ccff",
name = "Subtropical disturbance",
name = "Subtropical disturbance",
sortkey = 55
sortkey = 55,
icon = "[[File:Disturbance icon.png|25px]]"
},
},
extratropical = {
extratropical = {
Line 302: Line 303:
color = "00faf4",
color = "00faf4",
name = "Moderate tropical storm",
name = "Moderate tropical storm",
sortkey = 30004
sortkey = 30004,
icon = "[[File:Moderate tropical storm icon.png|18px]]"
},
},
zodw = {
zodw = {
Line 368: Line 370:
cats["ss"] = cats["subtropical"]
cats["ss"] = cats["subtropical"]
cats["et"] = cats["extratropical"]
cats["et"] = cats["extratropical"]
cats["ex"] = cats["extratropical"]
cats["md"] = cats["monsoondepression"]
cats["md"] = cats["monsoondepression"]
cats["pt"] = cats["potential"]
cats["pt"] = cats["potential"]
cats["potentialtropicalcyclone"] = cats["potential"]
cats["potentialtropicalcyclone"] = cats["potential"]
cats["potentialtropicalcyclone"] = cats["potential"]
cats["potentialtropicalcyclone"] = cats["potential"]
cats["post"] = cats["remnant"]
cats["post"] = cats["posttropical"]
cats["post-tropical"] = cats["posttropical"]
cats["remnantlow"] = cats["remnant"]
cats["remnantlow"] = cats["remnant"]
cats["rl"] = cats["remnant"]
cats["rl"] = cats["remnant"]
Line 431: Line 435:
-- viable category.
-- viable category.
local icons = {
local icons = {
-- Ambiguious 5-tier scale
["1"] = cats["cat1"].icon,
["2"] = cats["cat2"].icon,
["3"] = cats["cat3"].icon,
["4"] = cats["cat4"].icon,
["5"] = cats["cat5"].icon,
-- FMS scale
["a5"] = cats["aus5"].icon,
["a4"] = cats["aus4"].icon,
["a3"] = cats["aus3"].icon,
["a2"] = cats["aus2"].icon,
["a1"] = cats["aus1"].icon,
-- Misc
["extratropical2"] = "[[File:Extratropical storm icon.png|18px]]",
["extratropical2"] = "[[File:Extratropical storm icon.png|18px]]",
["typhoon"] = "[[File:Typhoon icon.png|18px]]",
["typhoon"] = "[[File:Typhoon icon.png|18px]]",
Line 440: Line 457:
icons["ex2"] = icons["extratropical2"]
icons["ex2"] = icons["extratropical2"]
icons["ty"] = icons["typhoon"]
icons["ty"] = icons["typhoon"]
icons["sts-s"] = icons["swiosts"]
icons["stss"] = icons["swiosts"]
icons["tc"] = icons["swiotc"]
-- Disabled due to improper ambiguity.
-- icons["tc"] = icons["swiosts"]


function p.color(frame)
function p.color(frame)
Line 502: Line 518:
or defaultCategory
or defaultCategory
return icons[iconCode] or ((cats[iconCode] or cats[defaultCategory]).icon) or cats["tropicalcyclone"].icon
return icons[icon] or (cats[icon] ~= nil and (
cats[icon].icon or cats["tropicalcyclone"].icon
) or cats[defaultCategory].icon)
end
end



Revision as of 06:03, 23 January 2022

local colorRatio = require("Module:Color contrast")._ratio
local TableTools = require("Module:TableTools")
local p = {}

-- Define categories

-- All sortkeys have been guessed. They may be changed by common sense
-- or consensus at [[Wikipedia talk:WikiProject Tropical cyclones]]
local cats = {
	severe = {
		color = "ccffff",
		name = "Severe tropical storm",
		sortkey = 120,
		icon = "[[File:Severe tropical storm icon.png|18px]]"
	},
	mtstorm = {
		 color = "00faf4", -- Old {{storm colour}} did not support. Value guessed.
		 name = "Moderate tropical storm",
		 sortkey = 105,
		 icon = "[[File:Moderate tropical storm icon.png|18px]]"
	},
	storm = {
		color = "00faf4",
		name = "Tropical storm",
		sortkey = 100,
		icon = "[[File:Tropical storm icon.png|18px]]"
	},
	severedep = {
		color = "5ebaff", -- Old {{storm colour}} did not support. Value guessed.
		name = "Severe tropical depression",
		sortkey = 120
	},
	moddepression = {
		color = "5ebaff", -- Old {{storm colour}} did not support. Value guessed.
		name = "Moderate tropical depression",
		sortkey = 95
	},
	depression = {
		color = "5ebaff",
		name = "Tropical depression",
		sortkey = 90,
		icon = "[[File:Tropical Depression.png|25px]]"
	},
	disturbance = {
		color = "80ccff",
		name = "Tropical disturbance",
		sortkey = 80,
		icon = "[[File:Disturbance icon.png|25px]]"
	},
	subtropicalcyclone = {
		color = "00faf4",
		name = "Subtropical cyclone",
		sortkey = 70
	},
	subtropical = {
		color = "00faf4",
		name = "Subtropical storm",
		sortkey = 65,
		icon = "[[File:Subtropical storm icon.png|18px]]"
	},
	subdepression = {
		color = "5ebaff",
		name = "Subtropical depression",
		sortkey = 60,
		icon = "[[File:Subtropical Depression icon.png|25px]]"
	},
	subdisturbance = {
		color = "80ccff",
		name = "Subtropical disturbance",
		sortkey = 55,
		icon = "[[File:Disturbance icon.png|25px]]"
	},
	extratropical = {
		color = "cccccc",
		name = "Extratropical cyclone",
		sortkey = 50,
		icon = "[[File:Extratropical cyclone icon.png|18px]]"
	},
	monsoondepression = {
		color = "5ebaff",
		name = "Monsoon depression",
		sortkey = 30,
		icon = "[[File:Monsoon Depression icon.png|25px]]"
	},
	potential = {
		color = "80ccff",
		name = "Potential tropical cyclone",
		sortkey = 25,
		icon = "[[File:Potential tropical cyclone icon.png|25px]]"
	},
	posttropical = {
		color = "cccccc",
		name = "Post-tropical depression",
		sortkey = 70,
		icon = "[[File:Post tropical cyclone icon.png|25px]]"
	},
	remnant = {
		color = "cccccc",
		name = "Post-tropical depression",
		sortkey = 70,
		icon = "[[File:Remnant low icon.png|25px]]"
	},
	unknown = {
		color = "c0c0c0",
		name = "Unknown strength tropical cyclone",
		sortkey = 0,
		icon = "'''?'''"
	},
	cat5 = {
		color = "ff6060",
		name = {
			atl = "Category 5 hurricane",
			epac = "Category 5 hurricane",
			satl = "Category 5 hurricane",
			wpac = "Category 5-equivalent super typhoon",
			default = "Category 5-equivalent tropical cyclone"
		},
		sortkey = 80020,
		icon = "[[File:Category 5 hurricane icon.png|18px]]"
	},
	cat4 = {
		color = "ff8f20",
		name = {
			atl = "Category 4 hurricane",
			epac = "Category 4 hurricane",
			satl = "Category 4 hurricane",
			wpac = "Category 4-equivalent typhoon",
			default = "Category 4-equivalent tropical cyclone"
		},
		sortkey = 80008,
		icon = "[[File:Category 4 hurricane icon.png|18px]]"
	},
	cat3 = {
		color = "ffc140",
		name = {
			atl = "Category 3 hurricane",
			epac = "Category 3 hurricane",
			satl = "Category 3 hurricane",
			wpac = "Category 3-equivalent typhoon",
			default = "Category 3-equivalent tropical cyclone"
		},
		sortkey = 80006,
		icon = "[[File:Category 3 hurricane icon.png|18px]]"
	},
	cat2 = {
		color = "ffe775",
		name = {
			atl = "Category 2 hurricane",
			epac = "Category 2 hurricane",
			satl = "Category 2 hurricane",
			wpac = "Category 2-equivalent typhoon",
			default = "Category 2-equivalent tropical cyclone"
		},
		sortkey = 80004,
		icon = "[[File:Category 2 hurricane icon.png|18px]]"
	},
	cat1 = {
		color = "ffffcc",
		name = {
			atl = "Category 1 hurricane",
			epac = "Category 1 hurricane",
			satl = "Category 1 hurricane",
			wpac = "Category 1-equivalent typhoon",
			default = "Category 1-equivalent tropical cyclone"
		},
		sortkey = 80002,
		icon = "[[File:Category 1 hurricane icon.png|18px]]"
	},
	supertyphoon = {
		color = "ff6060",
		name = "Category 4 super typhoon",
		sortkey = 80015
	},
	typhoon = {
		color = "fdaf9a",
		name = "Typhoon",
		sortkey = 60010
	},
	vstyphoon = {
		color = "fe887d",
		name = "Very strong typhoon",
		sortkey = 60020
	},
	vityphoon = {
		color = "ff6060",
		name = "Violent typhoon",
		sortkey = 60030
	},
	-- PAGASA
	styphoon = {
		color = "ff6060",
		name = "Supertyphoon",
		sortkey = 60050
	},
	sprcyclstorm = {
		color = "ff6060",
		name = "Super cyclonic storm",
		sortkey = 50020,
		icon = "[[File:Super cyclonic storm icon.png|18px]]"
	},
	esvrcyclstorm = {
		color = "ffc140",
		name = "Extremely severe cyclonic storm",
		sortkey = 50015,
		icon = "[[File:Extremely severe cyclonic storm icon.png|18px]]"
	},
	vsvrcyclstorm = {
		color = "ffffcc",
		name = "Very severe cyclonic storm",
		sortkey = 50010,
		icon = "[[File:Very severe cyclonic storm icon.png|18px]]"
	},
	svrcyclstorm = {
		color = "ccffff",
		name = "Severe cyclonic storm",
		sortkey = 50008,
		icon = "[[File:Severe cyclonic storm icon.png|18px]]"
	},
	niocyclone = {
		color = "00faf4",
		name = "Cyclonic storm",
		sortkey = 50006,
		icon = "[[File:Cyclonic storm icon.png|18px]]"
	},
	deepdepression = {
		color = "5ebaff",
		name = "Deep depression",
		sortkey = 50002,
		icon = "[[File:Deep depression icon.png|25px]]"
	},
	niodepression = {
		color = "80ccff",
		name = "Depression",
		sortkey = 50001,
		icon = "[[File:Depression icon.png|25px]]"
	},
	nioland = {
		color = "80ccff",
		name = "Land depression",
		sortkey = 50000,
		icon = "[[File:Land depression icon.png|25px]]"
	},
	aus5 = {
		color = "ff6060",
		name = "Category 5 severe tropical cyclone",
		sortkey = 40015,
		icon = "[[File:Aus 5 icon.png|18px]]"
	},
	aus4 = {
		color = "ffc140",
		name = "Category 4 severe tropical cyclone",
		sortkey = 40013,
		icon = "[[File:Aus 4 icon.png|18px]]"
	},
	aus3 = {
		color = "ffffcc",
		name = "Category 3 severe tropical cyclone",
		sortkey = 40010,
		icon = "[[File:Aus 3 icon.png|18px]]"
	},
	aus2 = {
		color = "ccffff",
		name = "Category 2 tropical cyclone",
		sortkey = 40008,
		icon = "[[File:Aus 2 icon.png|18px]]"
	},
	aus1 = {
		color = "00faf4",
		name = "Category 1 tropical cyclone",
		sortkey = 40006,
		icon = "[[File:Aus 1 icon.png|18px]]"
	},
	low = {
		color = "5ebaff",
		name = "Tropical low",
		sortkey = 40002,
		icon = "[[File:Tropical Low.png|25px]]"
	},
	sublow = {
		color = "5ebaff",
		name = "Subtropical low",
		sortkey = 40000
	},
	vintense = {
		color = "ff6060",
		name = "Very intense tropical cyclone",
		sortkey = 30010,
		icon = "[[File:Very intense tropical cyclone icon.png|18px]]"
	},
	intense = {
		color = "ffc140",
		name = "Intense tropical cyclone",
		sortkey = 30008,
		icon = "[[File:Intense tropical cyclone icon.png|18px]]"
	},
	tropicalcyclone = {
		color = "ffffcc",
		name = "Tropical cyclone",
		sortkey = 30006,
		icon = "'''TC'''"
	},
	mstorm = {
		color = "00faf4",
		name = "Moderate tropical storm",
		sortkey = 30004,
		icon = "[[File:Moderate tropical storm icon.png|18px]]"
	},
	zodw = {
		color = "80ccff",
		name = "Zone of disturbed weather",
		sortkey = 30000,
		icon = "[[File:Disturbed weather icon.png|25px]]"
	},
	shemsvrtc = {
		color = "ffe775",
		name = "Severe tropical cyclone",
		sortkey = 20020
	},
	shem5 = {
		color = "ff6060",
		name = "Severe tropical cyclone",
		sortkey = 20010
	},
	shem4 = {
		color = "ffc140",
		name = "Tropical cyclone",
		sortkey = 20008
	},
	shem2 = {
		color = "ccffff",
		name = "Tropical cyclone",
		sortkey = 20008
	},
	shem1 = {
		color = "00faf4",
		name = "Tropical cyclone",
		sortkey = 20008
	}
}

-- Default

local defaultCategory = "unknown"

-- Define aliases

-- All non-alphanumeric characters are already stripped, and the string is
-- already set to lowercase, so additional aliases for those are no longer
-- required.

cats["sty"] = cats["supertyphoon"]
cats["nwpsevere"] = cats["severe"]
cats["strong"] = cats["severe"]
cats["swiosts"] = cats["severe"]
cats["sts"] = cats["severe"]
cats["severets"] = cats["severe"]
cats["ty"] = cats["typhoon"]
cats["td"] = cats["depression"]
cats["ts"] = cats["storm"]
cats["nwpstorm"] = cats["storm"]
cats["d"] = cats["depression"]
cats["spdepression"] = cats["depression"]
cats["shdepression"] = cats["depression"]
cats["shemdepression"] = cats["depression"]
cats["swiodepression"] = cats["depression"]
cats["spdepression"] = cats["depression"]
cats["nwpdepression"] = cats["depression"]
cats["swsubdep"] = cats["subdepression"]
cats["sd"] = cats["subdepression"]
cats["ss"] = cats["subtropical"]
cats["et"] = cats["extratropical"]
cats["ex"] = cats["extratropical"]
cats["md"] = cats["monsoondepression"]
cats["pt"] = cats["potential"]
cats["potentialtropicalcyclone"] = cats["potential"]
cats["potentialtropicalcyclone"] = cats["potential"]
cats["post"] = cats["posttropical"]
cats["post-tropical"] = cats["posttropical"]
cats["remnantlow"] = cats["remnant"]
cats["rl"] = cats["remnant"]
cats["sucs"] = cats["sprcyclstorm"]
cats["escs"] = cats["esvrcyclstorm"]
cats["vscs"] = cats["vsvrcyclstorm"]
cats["scs"] = cats["svrcyclstorm"]
cats["cs"] = cats["niocyclone"]
cats["dd"] = cats["deepdepression"]
cats["cyclstorm"] = cats["niocyclone"]
cats["landdepression"] = cats["nioland"]
cats["land"] = cats["nioland"]
cats["ld"] = cats["nioland"]
cats["fiji5"] = cats["aus5"]
cats["fiji4"] = cats["aus4"]
cats["fiji3"] = cats["aus3"]
cats["fiji2"] = cats["aus2"]
cats["fiji1"] = cats["aus1"]
cats["tl"] = cats["low"]
cats["di"] = cats["disturbance"]
cats["swiodisturbance"] = cats["disturbance"]
cats["mts"] = cats["mstorm"]
cats["sub"] = cats["mstorm"]
cats["vitc"] = cats["vintense"]
cats["itc"] = cats["intense"]
cats["tc"] = cats["tropicalcyclone"]
cats["swiotc"] = cats["tropicalcyclone"]
cats["shem3"] = cats["tropicalcyclone"]
cats["shemtc"] = cats["tropicalcyclone"]

-- Ambiguous color names. This is because the old {[storm colour}} itself is 
-- extremely ambiguous. This is also used to override the output of some codes.
local colors = {
	-- Basin colors
	["5"] = cats["cat5"].color,
	["4"] = cats["cat4"].color,
	["3"] = cats["cat3"].color,
	["2"] = cats["cat2"].color,
	["1"] = cats["cat1"].color,
	-- Enhanced Fujita scale
	["ef5"] = cats["cat5"].color,
	["ef4"] = cats["cat4"].color,
	["ef3"] = cats["cat3"].color,
	["ef2"] = cats["cat2"].color,
	["ef1"] = cats["cat1"].color,
	["ef0"] = cats["ts"].color,
	-- Depression
	["d"] = "80ccff",
	-- FMS scale
	["a5"] = cats["aus5"].color,
	["a4"] = cats["aus4"].color,
	["a3"] = cats["aus3"].color,
	["a2"] = cats["aus2"].color,
	["a1"] = cats["aus1"].color
}

-- Icon overrides for old {{Tropical cyclone status icons}} that don't have a
-- viable category.
local icons = {
	-- Ambiguious 5-tier scale
	["1"] = cats["cat1"].icon,
	["2"] = cats["cat2"].icon,
	["3"] = cats["cat3"].icon,
	["4"] = cats["cat4"].icon,
	["5"] = cats["cat5"].icon,
	-- FMS scale
	["a5"] = cats["aus5"].icon,
	["a4"] = cats["aus4"].icon,
	["a3"] = cats["aus3"].icon,
	["a2"] = cats["aus2"].icon,
	["a1"] = cats["aus1"].icon,
	-- Misc
	["extratropical2"] = "[[File:Extratropical storm icon.png|18px]]",
	["typhoon"] = "[[File:Typhoon icon.png|18px]]",
	["swiosts"] = "[[File:Severe tropical storm south icon.png|18px]]",
	["swiotc"] = "[[File:SWIO tropical cyclone icon.png|18px]]"
}

icons["et2"] = icons["extratropical2"]
icons["ex2"] = icons["extratropical2"]
icons["ty"] = icons["typhoon"]
icons["stss"] = icons["swiosts"]
icons["tc"] = icons["swiotc"]

function p.color(frame)
	return p._color(frame.args[1] or frame:getParent().args[1])
end

function p.name(frame)
	return p._name(
		frame.args[1] or frame:getParent().args[1],
		frame.args[2] or frame:getParent().args[2]
	)
end

function p.sortkey(frame)
	return p._sortkey(frame.args[1] or frame:getParent().args[1])
end

function p.icon(frame)
	return p._icon(frame.args[1] or frame:getParent().args[1])
end

function p._color(colorCode)
	-- This looks confusing, but it's actually nested ternaries (for nil checks)
	local color = (colorCode ~= nil and string.len(colorCode) ~= 0) and 
		string.gsub(string.lower(colorCode), "[^%w]", "")
		or defaultCategory
		
	return colors[color] or ((cats[color] or cats[defaultCategory]).color)
end

function p._name(category, basin)
	local name_def = (cats[
		(category ~= nil and string.len(category) ~= 0) and 
			string.gsub(string.lower(category), "[^%w]", "")
			or defaultCategory
	] or cats[defaultCategory]).name
	return type(name_def) == "table" and 
		(
			name_def[string.lower(basin or "default")]
			or name_def["default"]
			or error("No default name for basin-based category name.")
		) 
		or name_def
end

function p._sortkey(category)
	-- This looks confusing, but it's actually nested ternaries (for nil checks)
	return (cats[
		(category ~= nil and string.len(category) ~= 0) and 
			string.gsub(string.lower(category), "[^%w]", "")
			or defaultCategory
	] or cats[defaultCategory]).sortkey
end

function p._icon(iconCode)
	-- This looks confusing, but it's actually nested ternaries (for nil checks)
	local icon = (iconCode ~= nil and string.len(iconCode) ~= 0) and 
		string.gsub(string.lower(iconCode), "[^%w]", "")
		or defaultCategory
		
	return icons[icon] or (cats[icon] ~= nil and (
		cats[icon].icon or cats["tropicalcyclone"].icon
	) or cats[defaultCategory].icon)
end

function count(_table)
	local n = 0
	for _ in pairs(_table) do
		n = n + 1
	end
	return n
end

function inlineWarning(details, icon)
	return mw.html.create("span")
		:css("color", "darkred")
		:wikitext("(")
		:node(
			mw.html.create("span")
				:attr("title", details)
				:css("text-decoration", "underline")
				:css("text-decoration-style", "dotted")
				:wikitext(icon or "!")
		)
		:wikitext(")")
end

function p.demo(frame)
	local out = mw.html.create("table")
		:addClass("wikitable")
		:addClass("sortable")
		:attr("style", "width: 100%")
		
	out
		:node(
			mw.html.create("tr")
				:node(mw.html.create("th"):wikitext("Icon")
					:attr("class", "unsortable")
					:attr("rowspan", "2")
					:css("width", "0"))
				:node(mw.html.create("th"):wikitext("ID")
					:attr("rowspan", "2"))
				:node(mw.html.create("th"):wikitext("Name")
					:attr("colspan", "2"))
				:node(mw.html.create("th"):wikitext("Color")
					:attr("rowspan", "2")
					:attr("colspan", "2"))
				:node(mw.html.create("th"):wikitext("Sortkey")
					:attr("colspan", "2"))
		):node(
			mw.html.create("tr")
				:node(mw.html.create("th"):wikitext("Basin"))
				:node(mw.html.create("th"):wikitext("Name"))
				:node(mw.html.create("th"):wikitext("Basin"))
				:node(mw.html.create("th"):wikitext("Sortkey")
					:attr("data-sort-type", "number"))
		)
	
	local legends = {}
	
	for name, cat in TableTools.sortedPairs(cats) do
		local rows = { mw.html.create("tr") }
		local row = rows[1]
		
		local actualIcon = p._icon(name)
		local icon = mw.html.create("td")
			:wikitext(actualIcon)
		if cat["icon"] ~= nil and actualIcon ~= cat["icon"] then
			icon
				:wikitext(" ")
				:node(
					mw.html.create("span")
						:css("text-decoration", "underline")
						:css("text-decoration-style", "dotted")
						:attr("title", "Overriden from original icon (" .. cat["icon"] .. ")")
						:wikitext("*")
				)
			legends["*"] = "Overriden from original icon."
		end
			
		local id = mw.html.create("td")
			:wikitext(name)
		local actualColor = p._color(name)
		local colorPreview = mw.html.create("td")
			:attr("style", "background-color: #" .. actualColor .. "; padding: 0; width: 1.8em")
		local color = mw.html.create("td")
			:wikitext("#" .. actualColor)
			
		if actualColor ~= cat["color"] then
			color
				:node(
					mw.html.create("span")
						:css("text-decoration", "underline")
						:css("text-decoration-style", "dotted")
						:attr("title", "Overriden from original color (#" .. cat["color"] .. ")")
						:wikitext("**")
				)
			legends["**"] = "Overriden from original color."
		end
		
		local catColorBlackRatio = colorRatio({ "#" .. actualColor, "black" })
		local catColorLinkRatio = colorRatio({ "#" .. actualColor, "#0645ad" })
		local catColorVisitedLinkRatio = colorRatio({ "#" .. actualColor, "#0b0080" })
		
		if catColorBlackRatio == "?" or catColorLinkRatio == "?" or catColorVisitedLinkRatio == "?" then
			color
				:wikitext(" ")
				:node(inlineWarning("This color must be a hexadecimal color.", "E"))
			legends["E"] = "Not a valid hexadecimal color code."
		else
			if catColorBlackRatio < 4.5 then
				color
					:wikitext(" ")
					:node(inlineWarning("This color has contrast issues with black (not WCAG 2.0 AA-compatible).", "C"))
				legends["C"] = "Color has contrast issues with black."
			end
			if catColorLinkRatio < 4.5 then
				color
					:wikitext(" ")
					:node(inlineWarning("This color has contrast issues with links (not WCAG 2.0 AA-compatible).", "L"))
				legends["L"] = "Color has contrast issues with links (#0645ad)."
			end
			if catColorVisitedLinkRatio < 4.5 then
				color
					:wikitext(" ")
					:node(inlineWarning("This color has contrast issues with visited links (not WCAG 2.0 AA-compatible).", "V"))
				legends["V"] = "Color has contrast issues with visited links (#0b0080)."
			end
		end
		
		local sortkeyCategory = mw.html.create("td")
			:attr("data-sort-value", cat["sortkey"])
		local sortkey = mw.html.create("td")
			:attr("data-sort-value", cat["sortkey"])
			:wikitext(cat["sortkey"])
			
		if cat["sortkey"] < 0 then
			sortkeyCategory:wikitext("Invalid")
		elseif cat["sortkey"] < 20000 then
			sortkeyCategory:wikitext("Global")
		elseif cat["sortkey"] < 30000 then
			sortkeyCategory:wikitext("Historical")
		elseif cat["sortkey"] < 40000 then
			sortkeyCategory:wikitext("SWIO")
		elseif cat["sortkey"] < 50000 then
			sortkeyCategory:wikitext("Aus/Fiji")
		elseif cat["sortkey"] < 60000 then
			sortkeyCategory:wikitext("NIO")
		elseif cat["sortkey"] < 80000 then
			sortkeyCategory:wikitext("WPAC")
		elseif cat["sortkey"] < 90000 then
			sortkeyCategory:wikitext("Atl/EPac/SAtl")
		elseif cat["sortkey"] < 100000 then
			sortkeyCategory:attr("style", "color: gray")
			sortkeyCategory:wikitext("''Global''")
		else
			sortkeyCategory:wikitext("Invalid")
		end
		
		if type(cat["name"]) == "string" then
			local name = mw.html.create("td")
				:attr("colspan", "2")
				:wikitext(cat["name"])
			row:node(icon)
			row:node(id)
			row:node(name)
		else
			local nameTableLength = count(cat["name"])
			icon:attr("rowspan", nameTableLength)
			id:attr("rowspan", nameTableLength)
			colorPreview:attr("rowspan", nameTableLength)
			color:attr("rowspan", nameTableLength)
			sortkeyCategory:attr("rowspan", nameTableLength)
			sortkey:attr("rowspan", nameTableLength)
			
			row:node(icon)
			row:node(id)
			local firstDone = false
			for key, basinName in TableTools.sortedPairs(cat["name"]) do
				if firstDone then
					local nameRow = mw.html.create("tr")
					
					nameRow
						:node(mw.html.create("td"):wikitext(key))
						:node(mw.html.create("td"):wikitext(basinName))
						
					table.insert(rows, nameRow)	
				else
					firstDone = true
					row
						:node(mw.html.create("td"):wikitext(key))
						:node(mw.html.create("td"):wikitext(basinName))
				end
			end
		end
		
		row:node(colorPreview)
		row:node(color)
		row:node(sortkeyCategory)
		row:node(sortkey)
		
		for _, _row in TableTools.sortedPairs(rows) do
			out:node(_row)
		end
	end
	
	for name, color in TableTools.sortedPairs(colors) do
		if cats[name] == nil then
			local row = mw.html.create("tr")
				:node(mw.html.create("td")
					:wikitext("''<span style=\"color:gray\">N/A</span>''"))
				:node(mw.html.create("td"):wikitext(name))
				:node(mw.html.create("td")
					:attr("colspan", "2")
					:wikitext("''<span style=\"color:gray\">not available</span>''"))
				:node(mw.html.create("td")
					:attr("style", "background-color: #" .. color .. "; padding: 0; width: 1.8em"))
				:node(mw.html.create("td")
					:wikitext("#" .. color))
				:node(mw.html.create("td")
					:attr("colspan", "2")
					:wikitext("''<span style=\"color:gray\">not available</span>''"))
			out:node(row)
		end
	end
	
	for name, icon in TableTools.sortedPairs(icons) do
		if cats[name] == nil then
			local row = mw.html.create("tr")
				:node(mw.html.create("td")
					:wikitext(icons[name]))
				:node(mw.html.create("td"):wikitext(name))
				:node(mw.html.create("td")
					:attr("colspan", "6")
					:wikitext("''<span style=\"color:gray\">not available</span>''"))
			out:node(row)
		end
	end
	
	local legendsCompiled = mw.html.create("ul")
	local showLegend = false
	legendsCompiled
		:css("list-style-icon", "none")
		:css("list-style-type", "none")
	for legend, details in TableTools.sortedPairs(legends) do
		showLegend = true
		legendsCompiled:node(
			mw.html.create("li")
				-- en dash
				:wikitext("'''" .. legend .. "''' – " .. details)
		)
	end
	
	return (showLegend and tostring(legendsCompiled) or "") .. tostring(out)
end

p.cats = cats

return p