Module:Television ratings graph

-- This module implements {{Television ratings graph}}.

local contrast_ratio = require('Module:Color contrast')._ratio

--------------------------------------------------------------------------------

-- TVRG class

-- The main class.

--------------------------------------------------------------------------------

local TVRG = {}

-- Allow usages of {{N/A}} cells

function TVRG.NACell(frame,text)

local cell = mw.html.create('td')

local attrMatch = '([%a-]*)="([^"]*)"'

infoParam = frame:expandTemplate{title='N/A',args={text}}

-- Gather styles of {{N/A}} and assign to node variable

while true do

local a,b = string.match(infoParam,attrMatch)

if a == nil or b == nil then break end

cell:attr(a,b)

infoParam = string.gsub(infoParam,attrMatch,'',1)

end

infoParam = string.gsub(infoParam,'%s*|%s*','',1)

cell:wikitext(infoParam)

return cell

end

-- Create the graph and table

function TVRG.new(frame,args)

args = args or {}

local categories = ''

local title = mw.title.getCurrentTitle()

-- Variables

local timeline = ''

local longestseason = -1

local average = args.average and 1 or 0

local season_title = args.season_title or 'Season'

local root = mw.html.create('div')

:attr('align', 'center')

-- Create the timeline

-- Number of actual viewer numbers

local numberargs = 0

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 numberargs = numberargs + 1 end

end

-- Determine number of seasons

local num_seasons = -1

for k,v in pairs(args) do

local thisseason = tonumber(string.sub(k,6))

if string.sub(k,1,5) == 'color' and thisseason > num_seasons then

num_seasons = thisseason

end

end

if num_seasons < 1 then

num_seasons = 1

end

-- Determine number of episodes and subtract averages if included (they should be equal to the number of seasons)

local num_episodes

if average == 1 then

num_episodes = numberargs-num_seasons

else

num_episodes = numberargs

end

-- Bar and graph width

local barwidth

if args.bar_width then barwidth = args.bar_width

elseif num_episodes >= 120 then barwidth = 6

elseif num_episodes >= 100 then barwidth = 7

elseif num_episodes >= 80 then barwidth = 8

elseif num_episodes >= 50 then barwidth = 9

elseif num_episodes >= 20 then barwidth = 10

else barwidth = 11

end

-- Determine maximum viewer figure

local maxviewers = -1

local multiple = 'millions'

for k,v in pairs(args) do

local num = tonumber(v)

if tonumber(k) ~= nil and num ~= nil and num > maxviewers then

maxviewers = num

end

end

if maxviewers <= 1.5 then

multiple = 'thousands'

maxviewers = maxviewers*1000

for k, v in pairs(args) do

local num = tonumber(v)

if tonumber(k) ~= nil and num ~= nil then args[k] = tostring(num*1000) end

end

end

local is_singular = num_episodes == 1

-- Basis parameters

timeline = timeline .. "\nImageSize = width:" .. (is_singular and 200 or 'auto') .. " height:"..(args.height or 500).. (is_singular and '' or " barincrement:"..(barwidth+(num_episodes == 2 and 16 or 4))).."\n"

timeline = timeline .. "\nPlotArea = left:50 bottom:50 top:10 right:100\n"

timeline = timeline .. "\nAlignbars = justify\n"

timeline = timeline .. "\nPeriod = from:0 till:"..math.ceil(maxviewers).."\n"

timeline = timeline .. "\nTimeAxis = orientation:vertical format:y\n"

-- Colors

timeline = timeline .. "\nColors =\n"

for season = 1,num_seasons do

args["color" .. season] = args["color" .. season] or '#CCCCFF'

hex = args["color" .. season]:gsub("#","")

if #hex == 3 then

-- If it's 3 hex chars then make it six instead

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)

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

local legend = season_title.."_"..(args["legend" .. season] and args["legend" .. season] or season )

timeline = timeline .. "\n id:season"..season.." value:rgb("..rgbR..", "..rgbG..", "..rgbB..") legend:"..legend:gsub(" ", "_").."\n"

end

timeline = timeline .. "\n id:bars value:gray(0.95)"

timeline = timeline .. "\nLegend = orientation:vertical position:right\n"

timeline = timeline .. "\nBackgroundColors = bars:bars\n"

timeline = timeline .. "\nScaleMajor = unit:year increment:"..(multiple == 'thousands' and 100 or 1).." start:0\n"

timeline = timeline .. "\nScaleMinor = unit:year increment:"..(multiple == 'thousands' and 100 or 1).." start:0\n"

local x_intervals = args.x_intervals or (num_episodes >= 80 and 2 or 1)

timeline = timeline .. "\nBarData=\n"

for episode = 1,num_episodes do

episodetext = ((episode % x_intervals == 0) and episode or "")

timeline = timeline .. "\n bar:"..episode.." text:"..episodetext.."\n"

end

-- Add bars to timeline, one per viewer figure

local bar = 1

local season = 0

local thisseason = 0

local counted_episodes = 0

timeline = timeline .. "\nPlotData=\n"

timeline = timeline .. "\n width:"..barwidth.." textcolor:black align:left anchor:from shift:(10,-4)\n"

for k,v in ipairs(args) do

if string.lower(v) == 'n/a' then v = '' end

if v == '-' then

-- Hyphen means new season

season = season + 1

-- Determine highest number of counted_episodes in a season

if thisseason > longestseason then

longestseason = thisseason

end

thisseason = 0

elseif average == 0 or (average == 1 and args[k+1] ~= '-' and args[k+1] ~= nil) then

-- Include bar for viewer figure, do not include if averages are included and the next parameter is a new season marker

timeline = timeline .. "\n bar:"..bar.." from:0 till:"..(v ~= '' and v or 0).." color:season"..season.."\n"

-- Increment tracking variables

counted_episodes = counted_episodes + 1

thisseason = thisseason + 1

bar = bar + 1

end

end

-- Determine highest number of episodes in a season after final season's bars

if thisseason > longestseason then

longestseason = thisseason

end

-- Axis labels

local countryDisplayUS, countryDisplayUK, countryDisplayOther

if args.country ~= nil and args.country ~= '' then

if args.country == "U.S." or args.country == "US" or args.country == "United States" then countryDisplayUS = 'U.S.'

elseif args.country == "U.K." or args.country == "UK" or args.country == "United Kingdom" then countryDisplayUK = 'UK'

else countryDisplayOther = args.country end

end

-- If there's a title, add it with the viewers caption, else just display the viewers caption by itself

if args.title ~= nil and args.title ~= '' then

root:wikitext("" .. args.title .. "" .. " " .. ": " .. ((countryDisplayUS or countryDisplayUK or countryDisplayOther) or "") .. ((countryDisplayUS or countryDisplayUK or countryDisplayOther) and " v" or "V") .. "iewers per episode (" .. multiple .. ")"):css('margin-top', '1em')

else

root:wikitext("Viewers per episode (" .. multiple .. ")"):css('margin-top', '1em')

end

root:tag('div'):css('clear','both')

-- Add timeline to div

if args.no_graph == nil then

if num_episodes > 100 then

root:tag('div'):wikitext("Too many episodes to display graph (maximum 100).")

if title.namespace == 0 then

categories = categories .. 'Category:Articles using Template:Television ratings graph with excessive figures'

end

else

timelineBase = frame:preprocess(""..timeline.."")

root:node(timelineBase)

root:tag('div'):css('clear','both')

end

end

-- Create ratings table

if args.no_table == nil then

local rtable = mw.html.create('table')

:addClass('wikitable')

:css('text-align', 'center')

-- Create headers rows

local row = rtable:tag('tr')

row:tag('th'):wikitext(season_title)

:attr('scope','col')

:attr('colspan','2')

:attr('rowspan','2')

:css('padding-left', '.8em')

:css('padding-right', '.8em')

row:tag('th')

:attr('scope','colgroup')

:attr('colspan',longestseason)

:wikitext("Episode number")

:css('padding-left', '.8em')

:css('padding-right', '.8em')

-- Average column

if average == 1 then

row:tag('th')

:attr('scope','col')

:attr('rowspan','2')

:wikitext("Average")

:css('padding-left', '.8em')

:css('padding-right', '.8em')

end

local row = rtable:tag('tr')

for i = 1,longestseason do

row:tag('th')

:attr('scope','col')

:wikitext(i)

end

local season = 1

local thisseason = 0

-- Create table rows and cells

for k,v in pairs(args) do

if tonumber(k) ~= nil then

-- New season marker, or final episode rating

if v == '-' or (average == 1 and args[k+1] == nil) then

if season > 1 then

-- Spanning empty cells with {{N/A}}

if thisseason < longestseason then

row:node(TVRG.NACell(frame,"–"):attr('colspan',longestseason-thisseason))

end

if average == 1 then

-- If averages included, then set the averages cell with value or TBD

if v ~= '' then

row:tag('td'):wikitext(args[k+1] ~= nil and args[k-1] or v)

else

row:node(TVRG.NACell(frame,"TBD"))

end

thisseason = thisseason + 1

end

end

-- New season marker

if v == '-' then

-- New row with default or preset caption

row = rtable:tag('tr')

row:tag('th')

:css('background-color', args['color' .. season])

:css('width','14px')

:css('padding','0')

row:tag('th')

:attr('scope','row')

:wikitext(args["legend" .. season] and args["legend" .. season] or season)

thisseason = 0

season = season + 1

end

elseif average == 0 or (average == 1 and args[k+1] ~= '-' and args[k+1] ~= nil) then

-- Viewer figures, either as a number or TBD

if string.lower(v) == 'n/a' then

row:node(TVRG.NACell(frame,"N/A"))

elseif v ~= '' then

row:tag('td'):wikitext(v)

:css('width', '35px')

else

row:node(TVRG.NACell(frame,"TBD"))

end

thisseason = thisseason + 1

end

end

end

-- Finish by checking if final row needs {{N/A}} cells

if average == 0 and thisseason < longestseason then

row:node(TVRG.NACell(frame,"–"):attr('colspan',longestseason-thisseason))

end

-- Add table to div root and return

root:node(rtable)

root:tag('div'):css('clear','both')

end

local current_monthyear = os.date("%B %Y")

local span = mw.html.create('span'):wikitext(frame:expandTemplate{title='Citation needed', args={date=current_monthyear}})

if countryDisplayUS then

root:wikitext("Audience measurement performed by Nielsen Media Research" .. (args.refs ~= '' and args.refs or tostring(span)))

elseif countryDisplayUK then

root:wikitext("Audience measurement performed by Broadcasters' Audience Research Board" .. (args.refs ~= '' and args.refs or tostring(span)))

else

root:wikitext("Source: " .. (args.refs ~= '' and args.refs or tostring(span)))

end

return tostring(root) .. categories

end

--------------------------------------------------------------------------------

-- Exports

--------------------------------------------------------------------------------

local p = {}

function p.main(frame)

local args = require('Module:Arguments').getArgs(frame, {

removeBlanks = false,

wrappers = 'Template:Television ratings graph'

})

return TVRG.new(frame,args)

end

return p