Documentation for this module may be created at Module:Television Rotten Tomatoes scores/doc

-- This module implements {{Television Rotten Tomatoes scores}}.

--------------------------------------------------------------------------------
-- TVRT class
-- The main class.
--------------------------------------------------------------------------------

local TVRT = {}

function TVRT.upper(str)
    return (str:gsub("^%l", string.upper))
end

function TVRT.round(value)
	return math.floor(value + 0.5)
end

-- Create the graph and table
function TVRT.new(frame,args)
	args = args or {}
	
	-- Variables
	local timeline = ""
	local episodes = 0
	local graph_width = args.width and tonumber(args.width) or 600
	local graph_height = args.height and tonumber(args.height) or 300
	local force_gap = args.forcegap or nil

	-- Create table and top caption
	local roottable = mw.html.create('table')
	roottable	
		:addClass('wikitable')
		:addClass(args.align and 'float'..args.align or '')
		:css('text-align', 'center')
		:css('width', (args.width or 600)..'px')
		:tag('caption'):wikitext('<i>'..(args.title or '')..'</i>'..(args.label and ' '..string.lower(args.label) or '')..': Critical reception by episode')
	
	local rootcell = roottable:tag('tr'):tag('td')
	
	-- Border widths 
	local g_l = 50
	local g_r = 50
	local g_t = 30
	local g_b = 40
	local g_lr_border = g_l + g_r
	local g_tb_border = g_t + g_b
	
	local score_min = 100
	local score_max = 0

	-- Number of episodes
	for k,v in pairs(args) do 
		if (string.lower(v) == 'n/a') or (not string.match(k,'[^%d]+') and not string.match(v,'[^%d%.]+')) then
			episodes = episodes + 1
			if v ~= "" and v ~= nil then
				score_min = math.min(score_min, tonumber(v))
				score_max = math.max(score_max, tonumber(v))
			end
		end
	end
	
	-- Position varibles for each entry 
	local old_x = g_l
	local old_y = 0
	local new_x = old_x
	local new_y = old_y
	local increment_x = TVRT.round((graph_width-g_lr_border)/(episodes-1))
	
	local period_min = 0
	local period_max = 100
	
	if args.minimize_y then
		period_min = math.floor(score_min/10)*10
		period_max = math.ceil(score_max/10)*10
	end
	
	-- One percentage on the y axis is this many pixels 
	local percent_pixel = (graph_height-g_tb_border)/(period_max-period_min)
	
	-- Create the timeline
	timeline = timeline .. "\nImageSize  = width:"..graph_width.." height:"..graph_height.."\n"
	timeline = timeline .. "\nPlotArea   = left:"..g_l.." bottom:"..g_b.." top:"..g_t.." right:"..g_r.."\n"
	timeline = timeline .. "\nTimeAxis   = orientation:vertical format:y\n"
	timeline = timeline .. "\nAlignBars  = justify\n"
	timeline = timeline .. "\nColors =\n"
	timeline = timeline .. "\n id:grayH value:gray(0.95)\n"
	timeline = timeline .. "\n id:grayV value:gray(0.85)\n"

	-- Colour from hex to 0-1 RGB
	local hex = args["color"]:gsub("#","")
	if #hex == 3 then
		hex = hex:sub(1,1) .. hex:sub(1,1) .. hex:sub(2,2) .. hex:sub(2,2) .. hex:sub(3,3) .. hex:sub(3,3) -- If it's 3 hex chars then make it six instead
	end
	rgbR = tonumber("0x"..hex:sub(1,2))/256
	rgbG = tonumber("0x"..hex:sub(3,4))/256
	rgbB = tonumber("0x"..hex:sub(5,6))/256
	timeline = timeline .. "\n id:graph value:rgb("..rgbR..", "..rgbG..", "..rgbB..")\n"
	
	-- Further the timeline 
	timeline = timeline .. "\nPeriod     = from:"..period_min.." till:"..period_max.."\n"
	timeline = timeline .. "\nScaleMajor = unit:year start:"..period_min.." increment:10\n"
	timeline = timeline .. "\nScaleMinor = unit:year start:"..period_min.." increment:2\n"

	-- X-axis position for each entry 
	timeline = timeline .. "\nBarData=\n"
	for episode = 1,episodes do 
		timeline = timeline .. "\n bar:"..episode.." text:"..episode.."\n"
	end
	
	-- Faint line at each 10% on the y-axis 
	timeline = timeline .. "\nLineData=\n"
	timeline = timeline .. "\n layer:front\n"
	for variance = period_min,period_max,10 do 
		timeline = timeline .. "\n at:"..variance.." frompos:"..old_x.." tillpos:"..(graph_width-g_r).." color:grayH width:1 layer:back\n"
	end
	
	-- Draw the lines between each entry 
	local all_positions = {}

	local last_x = nil
	local last_y = nil
	local current_x = g_l

	for episode = 1, episodes do
		local score = tonumber(args[episode])

		if score then
			local y = TVRT.round(g_b + (score-period_min) * percent_pixel)

			if last_x then
				timeline = timeline .. "\n points:("..last_x..","..last_y..")("..current_x..","..y..") color:graph width:1.5\n"
			end

			-- Draw square marker
			timeline = timeline .. "\n at:"..score.." frompos:"..(current_x-2).." tillpos:"..(current_x+2).." color:graph width:4\n"

			all_positions[#all_positions+1] = {x = current_x, y = y, episode = episode}

			last_x = current_x
			last_y = y
		elseif force_gap then
			last_x = nil
			last_y = nil
		end

		current_x = current_x + increment_x
	end

	-- Dud entry to allow positioning of final value
	all_positions[#all_positions+1] = {x = 1000, y = 1000, episode = nil}
	
	-- Draw the values on the graph
	local fontsize = 8
	local old_slope = 0
	local new_slope = old_slope
	
	timeline = timeline .. "\nTextData=\n"
	timeline = timeline .. "\n pos:("..(graph_width/2-20)..",15) fontsize:S text:Episode\n"
	timeline = timeline .. "\n pos:(20,"..(graph_height-15)..") fontsize:S text:Percent\n"
	for k,v in pairs(all_positions) do 
		-- Determine the position of the text based on the slope of each line 
		local nextv = all_positions[k+1]
		if nextv ~= nil then
			new_slope = (nextv.y-v.y) / (nextv.x-v.x)
			if new_slope > 0 then
				v.y = v.y - fontsize*1.5
			else
				v.y = v.y + fontsize*0.5
			end
			old_slope = new_slope
		end
		
		local label = v.episode and args[v.episode] or ""
		timeline = timeline .. "\n pos:("..v.x..","..v.y..") textcolor:black fontsize:"..fontsize.." text:"..label.."\n"
	end
	
	-- Add the timeline itself to the base table
	local timeline_base = frame:preprocess("<timeline>" .. timeline .. "</timeline>")
	rootcell:node(timeline_base)
	rootcell:tag('div'):css('clear','both')
	
	-- Create bottom caption
	local lower_caption_text1 = (args.label and TVRT.upper(string.lower(args.label)) or "<i>"..args.title.."</i>") .. " (" .. (args.year or "Year?") .. "):&nbsp;"
	local lower_caption_text2 = "Percentage of positive critics' reviews tracked by the website [[Rotten Tomatoes]]" .. (args.ref or "Ref?")
	
	local lower_caption1 = mw.html.create('span')
	local lower_caption2 = mw.html.create('span')
	
	lower_caption1
		:css('color', '#'..hex)
		:css('font-weight', 'bold')
		:wikitext(lower_caption_text1)
	lower_caption2
		:wikitext(lower_caption_text2)
	
	if not args.nolower then rootcell:node(lower_caption1) end
	rootcell:node(lower_caption2)

	return roottable
end

--------------------------------------------------------------------------------
-- Exports
--------------------------------------------------------------------------------

local p = {}

function p.main(frame)
	local args = require('Module:Arguments').getArgs(frame, {
		removeBlanks = false,
		wrappers = 'Template:Television Rotten Tomatoes scores'
	})
	return TVRT.new(frame,args)
end

return p