Relatedness Report.fh_lua

--[[
@Title: DNA Relatedness
@Author: Jane Taubman
@Version: 1.3
@LastUpdated: 30 Nov 2020 
@Description: Simple output report showing the relations of a person with the computed shared DNA

1.3 V7 Compatibility
]]
require "iuplua"
iup.SetGlobal("CUSTOMQUITMESSAGE","YES")

-- Settings
local TotalCM = 6800
-- percentage, min %, max %
local cmTable = {
{100,100,100},
{50,32,54},
{25,18,32},
{12.5,8,18.5},
{6.25,3,9},
{3.125,1,5},
{1.56,0.42,3},
{0.78,0,1.52},
{0.4,0,1}
}

function main()
    local tblIndi = fhPromptUserForRecordSel('INDI',1) 
	 if #tblIndi == 0 then return end

    local pMain = tblIndi[1]
    local tblResults = createResultTable()
	 -- V6 Template
    local template = "Gens%. Up=(%d+), Gens%. Down=(%d+), Half=(%d+), Spouse Start=(%d+), Spouse End=(%d+)"

    -- Define Columns
    tblResults.indi = {title='Record'}
    tblResults.id = {title='id',type='integer',align='align_right',width=20}
    tblResults.relationship = {title='Relationship'}
    tblResults.relatedness = {title='Relatedness',align='align_right',type='text',sort=1,sortAscending=false, width=40}
    tblResults.cm = {title='Expected CM',align='align_right',type='integer', width=30}
    tblResults.mincm = {title='Min CM',align='align_right',type='integer', width=30}
    tblResults.maxcm = {title='Max CM',align='align_right',type='integer',width=35}

	local ProgressBar = NewProgressBar()
	ProgressBar.Start( "Computing Relationships",countIndi())
		
    for pi in records('INDI') do
 		ProgressBar.Step()		-- Defaults to step by 1
 		if ProgressBar.Stop() then
 			break
 		end

        local i = 1
        local totalRelatedness = 0
        local relationship = ''
        repeat
        local code = fhCallBuiltInFunction('Relationship',pMain,pi,'CODE',i)
        local gensUp,gensDown,half,spouseStart,spouseEnd = string.match(code,template)
        if code == "" then -- no more relationships
            i = 0
        else
           if spouseStart == "0" and spouseEnd == "0" then -- related but not by marriage
               relationship = relationship..' '..i..'='..fhCallBuiltInFunction('Relationship',pMain,pi,'TEXT',i)
               local relatedness = .5^(math.abs(gensUp) + math.abs(gensDown))
               if half == "0" and gensUp > "0" and gensDown > "0" then
						--  Double if not Ancestor or Decendent and not step relationship
						relatedness = relatedness * 2
               end
               totalRelatedness = totalRelatedness + relatedness            
           end
           i = i + 1
        end
        until (i == 0)
        if totalRelatedness > 0 then
            -- Add Row
            tblResults:newRow()
            -- Set Columns
            tblResults.indi:set(pi:Clone())
            tblResults.relationship:set(relationship)
            tblResults.id:set(fhGetRecordId(pi))
				local totalpercent = totalRelatedness * 100 
            local padvalue = string.format("%12f",totalpercent)
            tblResults.relatedness:set(padvalue)
            
				tblResults.cm:set(TotalCM * totalRelatedness)
				-- Read table to find range
				local breakflg = false
				for key,value in ipairs(cmTable) do
					if totalpercent >= value[1] then 
						tblResults.mincm:set(value[2]/100*TotalCM)
						tblResults.maxcm:set(value[3]/100*TotalCM)
					   breakflg = true
					end
					if breakflg then break end
				end

   	 		ProgressBar.Message( tblResults:rowCount().." Genetic Relationships Found" )
        end
    end

ProgressBar.Message( "Outputing Results" )
	 local sTitle = "Genetic Relationships with "..fhGetDisplayText(pMain)
    fhOutputResultSetTitles(sTitle, sTitle, "Output Date: %#x")
    tblResults:outputResults()
   ProgressBar.Close()
   iup.Close()
end
function countIndi()
local count = 0
for pi in records('INDI') do
count  = count  + 1 
end
return count
end
function records(type)
    local pi = fhNewItemPtr()
    local p2 = fhNewItemPtr()
    pi:MoveToFirstRecord(type)
    return function ()
        p2:MoveTo(pi)
        pi:MoveNext()
        if p2:IsNotNull() then return p2 end
    end
end

function createResultTable()
    local tblOutput_mt = {} -- create metatable
    local iC = 0 -- Define count of lines
    local tblOutput = {} -- Define Columns Table
    tblOutput_mt.col = 0
    tblOutput_mt.seq = {}
    tblOutput_mt.__newindex =

    function (t,k,v)
        -- Set Values to Defaults if not supplied
        if v.content == nil then v.content = {} end
        if v.title == nil then v.title = k end
        if v.type == nil then v.type = 'notset' end
        if v.width == nil then v.width = 140 end
        if v.align == nil then v.align = 'align_left' end
        if v.sort == nil then v.sort = 0 end
        if v.sortAscending == nil then v.sortAscending = true end
        if v.sortType == nil then v.sortType = 'default' end
        if v.visibility == nil then v. visibility = 'show' end
        v.set =

        function(self,value)
            self.content[iC] = value
            if self.type == 'notset' then
                if type(value) == 'string' then
                    self.type = 'text'
                elseif type(value) == 'number' then
                    self.type = 'integer'
                    self.width = '30'
                else
                    self.type = 'item'
                end
            end
        end
        rawset(t,k,v) -- update original table
        local m = getmetatable(t)
        m.col = m.col + 1
        table.insert(m.seq,k)
    end
    tblOutput_mt.__call =

    function (t)
        local i = 0
        local m = getmetatable(t)
        local n = table.getn(m.seq)
        return function ()
            i = i + 1
            if i <= n then
                return t[m.seq[i]]
            end
        end
    end
    tblOutput.newRow = function(t)
        iC = iC + 1
    end
    tblOutput.rowCount = function(t)
        return iC
    end
    tblOutput.outputResults = function(self)
        for l in self() do
            fhOutputResultSetColumn(l.title, l.type, l.content, iC, l.width,l.align,l.sort,l.sortAscending,l.sortType,l.visibility )
        end
    end
    setmetatable(tblOutput, tblOutput_mt)
    return tblOutput
end
function table.getnx(t)
    local count = 0
    for _, __ in pairs(t) do
        count = count + 1
    end
    return count
end
    --[[
    @Title: Progress Bar (drop in)
    @Author: Jane Taubman / Mike Tate
    @LastUpdated: January 2013
    @Description: Allows easy adding of a Progress Bar to any long running Plugin
    ]]
     
    -- Progress Bar Function Prototype --
    function NewProgressBar(tblGauge)
     
    	local tblGauge	= tblGauge or {}				-- Optional table of external parameters
    	local strFont	= tblGauge.Font		or nil			-- Font dialogue default is current font
    	local strButton	= tblGauge.Button	or "255 0 0"		-- Button colour default is red
    	local strBehind	= tblGauge.Behind	or "255 255 255"	-- Background colour default is white
    	local intShowX	= tblGauge.ShowX	or iup.CENTER		-- Show window default position is central
    	local intShowY	= tblGauge.ShowY	or iup.CENTER
    	local intMax, intVal, intPercent, intStart, intDelta, intScale, strClock, isBarStop
    	local lblText, barGauge, lblDelta, btnStop, dlgGauge
     
    	local function doFocus()					-- Bring the Progress Bar window into Focus
    		dlgGauge.bringfront="YES"				-- If used too often, inhibits other windows scroll bars, etc
    	end -- local function doFocus
     
    	local function doUpdate()					-- Update the Progress Gauge and the Delta % with clock
    		barGauge.value = intVal
			local s = string.format("%4d %%      %s ",math.floor(intPercent),strClock)
    		lblDelta.title = s
    	end -- local function doUpdate
     
    	local function doReset()					-- Reset all dialogue variables and Update display
    		intVal		= 0					-- Current value of Progress Bar
    		intPercent	= 0				-- Percentage of progress
    		intStart	= os.time()				-- Start time of progress
    		intDelta	= 0					-- Delta time of progress
    		intScale	= math.ceil( intMax / 1000 )		-- Scale of percentage per second (this guess is corrected in Step function)
    		strClock	= "00 : 00 : 00"			-- Clock delta time display
    		isBarStop	= false					-- Stop button pressed signal
    		doUpdate()
    		doFocus()
    	end -- local function doReset
     
    	local tblProgressBar = {
     
    		Start = function(strTitle,intMaximum)			-- Create & start Progress Bar window
    			if not dlgGauge then
    				strTitle = strTitle or ""		-- Dialogue and button title
    				intMax = intMaximum or 100		-- Maximun range of Progress Bar, default is 100
    				local strSize = tostring( math.max( 100, string.len(" Stop "..strTitle) * 8 ) ).."x30"	-- Adjust Stop button size to Title
    				lblText  = iup.label	{ title=" ", expand="YES", alignment="ACENTER", tip="Progress Message" }
    				barGauge = iup.progressbar { rastersize="400x30", value=0, max=intMax, tip="Progress Bar" }
    				lblDelta = iup.label	{ title=" ", expand="YES", alignment="ACENTER", tip="Percentage and Elapsed Time" }
    				btnStop  = iup.button	{ title=" Stop "..strTitle, rastersize=strSize, fgcolor=strButton, tip="Stop Progress Button", action=function() isBarStop = true end }	-- Signal Stop button pressed	return iup.CLOSE -- Often caused main GUI to close !!!
    				dlgGauge = iup.dialog	{ title=strTitle.." Progress ", font=strFont, dialogframe="YES", background=strBehind,	-- Remove Windows minimize/maximize menu
    								iup.vbox{ alignment="ACENTER", gap="10", margin="10x10",
    									lblText,
    									barGauge,
    									lblDelta,
    									btnStop,
    								},
    								move_cb = function(self,x,y) tblGauge.ShowX = x tblGauge.ShowY = y end,
    								close_cb = btnStop.action,		-- Windows Close button = Stop button
    							}
    				dlgGauge:showxy(intShowX,intShowY)					-- Show the Progress Bar window
    				doReset()								-- Reset the Progress Bar display
    			end
    		end,
     
    		Message = function(strText)								-- Show the Progress Bar message
    			if dlgGauge then lblText.title = strText end
    		end,
     
    		Step = function(intStep)								-- Step the Progress Bar forward
    			if dlgGauge then
    				intVal = intVal + ( intStep or 1 )					-- Default step is 1
    				local intNew = math.ceil( intVal / intMax * 100 * intScale ) / intScale
    				if intPercent ~= intNew then						-- Update progress once per percent or per second, whichever is smaller
    					intPercent = math.max( 0, intNew )				-- Ensure percentage is greater than zero
    					if intVal > intMax then intVal = intMax intPercent = 100 end	-- Ensure values do not exceed maximum
    					intNew = os.difftime(os.time(),intStart)
    					if intDelta < intNew then					-- Update clock of elapsed time
    						intDelta = intNew
    						intScale = math.ceil( intDelta / intPercent )		-- Scale of seconds per percentage step
    						local intHour = math.floor( intDelta / 3600 )
    						local intMins = math.floor( intDelta / 60 - intHour * 60 )
    						local intSecs = intDelta - intMins * 60 - intHour * 3600
    						strClock = string.format("%02d : %02d : %02d",intHour,intMins,intSecs)
    					end
    					doUpdate()							-- Update the Progress Bar display
    				end
    				iup.LoopStep()
    			end
    		end,
     
    		Focus = function()
    			if dlgGauge then doFocus() end							-- Bring the Progress Bar window to front
    		end,
     
    		Reset = function()									-- Reset the Progress Bar display
    			if dlgGauge then doReset() end
    		end,
     
    		Stop = function()									-- Check if Stop button pressed
    			iup.LoopStep()
    			return isBarStop
    		end,
     
    		Close = function()									-- Close the Progress Bar window
    			isBarStop = false
    			if dlgGauge then dlgGauge:destroy() dlgGauge = nil end
				if iup.Close then 
					iup.Close() 
				end
    		end,
     
    	} -- end newProgressBar
    	return tblProgressBar
    end -- function NewProgressBar

if fhGetAppVersion() > 6 then 
function table.getn(t)
local count = 0
for _, __ in pairs(t) do
count = count + 1
end
return count
end

function unpack(t)
    return table.unpack(t)
end
end

----------------------------------
main()

Source:Relatedness-Report-1.fh_lua