*! version 1.0.0  30mar2026
*! midas_eforest — Exploratory forest plot (coupled, no summary diamond)
*! Split from midas_forest.ado
*! Author: Ben Adarkwa Dwamena, MD

capture program drop midas_eforest

program define midas_eforest, rclass byable(recall) sortpreserve
	version 15

	#delimit;
	syntax varlist(min=4 max=4 numeric) [if] [in],
		PLOTtype(string)
		[ID(varlist min=1 max=2)
		LEVEL(integer 95)
		TITLE(passthru)
		MScale(real 0.75)
		TEXTScale(real 1.0)
		COMBscale(real 0.5)
		CImethod(string) CIColor(string asis) DIAMcolor(string asis) *];
	#delimit cr

	if !inlist("`plottype'", "generic", "ellipse", "thick", "rain") {
		di as error "plottype() must be one of: generic, ellipse, thick, or rain"
		exit 198
	}

	// Check xsvmat
	capture which xsvmat
	if _rc {
		display as error "You need to install the xsvmat package"
		display as error ". {stata ssc install xsvmat, replace}"
		exit 198
	}

	qui {
		global alph = (100 - `level') / 200
		global midas_noobs = _N
	}

	// Parse varlist
	tokenize `varlist'
	local tp `1'
	local fp `2'
	local fn `3'
	local tn `4'

	// Build the varlist matrix that cforest expects
	marksample touse

	preserve
	qui keep if `touse'

	if "`id'" != "" {
		egen _midas_studylabel = concat(`id'), p(" ")
	}
	else {
		gen _midas_studylabel = string(_n)
	}

	// Create matrix with tp fp fn tn, row-named by study ID
	mkmat `tp' `fp' `fn' `tn', mat(foresters) rownames(_midas_studylabel)
	mat colnames foresters = tp fp fn tn

	restore

	cforest, plot(`plottype') title(`title') ci(`cimethod') ///
		ms(`mscale') level(`level') text(`textscale') combscale(`combscale') ///
		cicolor(`cicolor') diamcolor(`diamcolor')
end

capture program drop cforest

program define cforest, rclass byable(recall) sortpreserve
	version 13

	#delimit;
	syntax [if] [in],
		PLOTtype(string)
		[LEVEL(integer 95)
		TITLE(passthru)
		MScale(real 0.75)
		TEXTScale(real 1.0)
		COMBscale(real 0.5)
		CImethod(string) CIColor(string asis) DIAMcolor(string asis) *];
	#delimit cr

	qui {
		preserve
		local id: rowfullnames foresters
		local x: word count `id'
		tempvar StudyIds
		qui gen `StudyIds' = ""
		forvalues i = 1/`x' {
			local bb: word `i' of `id'
			qui replace `StudyIds' = "`bb'" in `i'
		}
		xsvmat double foresters, norestore names(col) rownames(`StudyIds')

		// Calculate study-specific sensitivity with CI
		tempvar sens sensvar sensse senslo senshi sss
		gen __midas_dis = tp + fn
		midas_cii __midas_dis tp, p(`sens') lowerci(`senslo') ///
			upperci(`senshi') cimethod(`cimethod') level(`level')
		gen double `sensse' = (`senshi' - `senslo') / (2 * invnormal(0.975))
		gen double `sensvar' = invnormal(0.975) * `sensse'

		// Calculate study-specific specificity with CI
		tempvar spec specvar specse speclo spechi
		gen __midas_ndis = tn + fp
		midas_cii __midas_ndis tn, p(`spec') lowerci(`speclo') ///
			upperci(`spechi') cimethod(`cimethod') level(`level')
		gen double `specse' = (`spechi' - `speclo') / (2 * invnormal(0.975))
		gen double `specvar' = invnormal(0.975) * `specse'

		gen `sss' = strlen(`StudyIds')
		sum `sss', meanonly
		local ss4 = int(r(max) + 50)
		format `StudyIds' %-`ss4's

		local gtitle1 "Sensitivity"
		local gtitle2 "Specificity"

		tempvar studyvar1 studyvar2 studyvar1lo studyvar1hi
		tempvar studyvar2lo studyvar2hi xvar
		tempname obs obs1 obs2 obs3 obs4
		tempname forplot1 forplot2 forplot ellip1 ellip2
		tempname generic1 generic2 thick1 thick2 rain1 rain2

		gen `obs' = _n
		gen `obs1' = _n
		gen `obs2' = _n
		gen `obs3' = _n
		gen `obs4' = _n
		gen `xvar' = 0
		gen double `studyvar1' = `sens'
		gen double `studyvar1lo' = `senslo'
		gen double `studyvar1hi' = `senshi'
		gen double `studyvar2' = `spec'
		gen double `studyvar2lo' = `speclo'
		gen double `studyvar2hi' = `spechi'
		gsort -`StudyIds'

		local null1: di " "
		count
		local max = r(N)
		local maxx = `max' + 2

		label value `obs' obs
		forval i = 1/`max' {
			local value = `"`value' `i'"'
			label define obs `i' "`=`StudyIds'[`i']'", modify
		}

		local ylab1 "labsize(*`textscale') tl(*2) labgap(*3) labc(bg) tlc(none)"
		local ylabopt "labsize(*`textscale') tl(*0) labgap(*3)"
		local xlab0 "xlab(0(.5)1.0, format(%2.1f) labsize(*`textscale') labc(bg) tlc(none))"
		local xlab1 "xlab(0(.5)1.0, format(%2.1f) labcolor(bg) tlcolor(bg)) xscale(lcolor(bg))"
		local xlab2 "xlab(0(.5)1.0, format(%2.1f) labsize(*`textscale'))"

		qui tostring `studyvar1lo' `studyvar1' `studyvar1hi', ///
			gen(`studyvar1lo'1 `studyvar1'1 `studyvar1hi'1) format(%3.2f) force
		replace `studyvar1lo'1 = " [" + `studyvar1lo'1 + " - "
		replace `studyvar1hi'1 = `studyvar1hi'1 + "]"
		egen studyvar1ci = concat(`studyvar1'1 `studyvar1lo'1 `studyvar1hi'1)
		label value `obs1' obs1

		forval i = 1/`max' {
			local value1 = `"`value' `i'"'
			label define obs1 `i' "`=studyvar1ci[`i']'", modify
		}

		qui tostring `studyvar2lo' `studyvar2' `studyvar2hi', ///
			gen(`studyvar2lo'1 `studyvar2'1 `studyvar2hi'1) format(%3.2f) force
		replace `studyvar2lo'1 = " [" + `studyvar2lo'1 + " - "
		replace `studyvar2hi'1 = `studyvar2hi'1 + "]"
		egen studyvar2ci = concat(`studyvar2'1 `studyvar2lo'1 `studyvar2hi'1)
		label value `obs2' obs2

		forval i = 1/`max' {
			local value2 = `"`value' `i'"'
			label define obs2 `i' "`=studyvar2ci[`i']'", modify
		}

		local null " "

		gen TP = tp
		gen TN = tn
		gen FP = fp
		gen FN = fn

		// Create 2x2 table plots
		foreach k of varlist TP FP FN TN {
			format `k' %9.0f
			egen s`k' = total(`k')
			local total = s`k'[_N]
			local sum: di "`total'"
			local contid: di "`k'"
			gen obs`k' = _n
			gen xvalue`k' = 0
			label value obs`k' obsk
			forval i = 1/`max' {
				local value`k' = `"`value' `i'"'
				label define obsk `i' "`=`k'[`i']'", modify
			}
			local graphlist "`graphlist' plot`k'"
			twoway (scatter obs`k' xvalue`k', ms(i) ///
				ylabel(`maxx' "{bf: `contid'}" `"`value`k''"', ///
				valuelabel `ylabopt' angle(360)) lpat(blank) `xlab0'), ///
				yscale(noline) fxsize(5) ///
				nodraw legend(off) yti("") ///
				plotregion(style(none)) graphregion(style(none)) ///
				xtitle("") name(plot`k', replace)
		}

		twoway (scatter `obs' `xvar', ms(i) ///
			ylabel(`maxx' "{bf: Studyid}" `"`value'"', ///
			valuelabel `ylabopt' angle(360)) ///
			lpat(blank) `xlab0'), xscale(noline) ///
			yscale(noline) ///
			nodraw legend(off) fxsize(20) yti("") xtitle("") ///
			plotregion(style(none)) graphregion(style(none)) ///
			name(`forplot', replace)

		twoway (scatter `obs1' `xvar', ms(i) ///
			ylab(`maxx' "{bf: `gtitle1' `midaconf'}" `"`value1'"', ///
			valuelabel labsize(*`textscale') labgap(*3) angle(360)) lpat(blank) `xlab0') ///
			, plotregion(style(none)) graphregion(style(none)) ///
			yscale(noline) nodraw legend(off) ///
			yti("") xtitle("") fxsize(22) ///
			name(`forplot1', replace) ylab(, noticks)

		twoway (scatter `obs2' `xvar', ms(i) ///
			ylab(`maxx' "{bf: `gtitle2' `midaconf'}" `"`value2'"', ///
			valuelabel labsize(*`textscale') labgap(*3) angle(360)) lpat(blank) `xlab0') ///
			, plotregion(style(none)) graphregion(style(none)) ///
			yscale(noline) nodraw legend(off) ylab(, noticks) ///
			yti("") xtitle("") fxsize(25) ///
			name(`forplot2', replace)

		// Create plots based on plot type
		if (strpos("`plottype'", "ellipse") != 0) {
			forv i = 1/`max' {
				range t`i' 0 `=2*c(pi)' 1000
				local a`i' = `senshi' - `sens' in `i'
				local xc`i' = `sens' in `i'
				local yc`i' = `i' in `i'
				gen xt`i' = max(`xc`i'' + `a`i'' * cos(t`i'), 0)
				gen yt`i' = `yc`i'' + sin(t`i')
				qui replace yt`i' = (yt`i' + `i') * .5
				local graph`i' `"(area yt`i' xt`i', lwidth(none) fcolor(blue))"'
				local xplot1 `"(scatteri `i' `xc`i'', msymbol(|) mcolor(black) msize(medium))"'
				local gellip "`gellip' `graph`i'' `xplot1'"
			}

			#delimit;
			graph twoway `gellip' (scatteri `maxx' 0.5 (3) "{bf: `gtitle1'}", ms(i) mlabc(black) mlabsize(*0.85)),
				ytitle("") xtitle("") fxsize(20) yscale(off) legend(off) plotregion(style(none))
				xlab(0(0.25)1) graphregion(style(none)) title("") nodraw name(`ellip1', replace);
			#delimit cr

			forv i = 1/`max' {
				range st`i' 0 `=2*c(pi)' 1000
				local sa`i' = `spechi' - `spec' in `i'
				local sxc`i' = `spec' in `i'
				local syc`i' = `i' in `i'
				gen sxt`i' = max(`sxc`i'' + `sa`i'' * cos(st`i'), 0)
				gen syt`i' = `syc`i'' + sin(t`i')
				qui replace syt`i' = (syt`i' + `i') * .5
				local sgraph`i' `"(area syt`i' sxt`i', lwidth(none) fcolor(blue))"'
				local xplot2 `"(scatteri `i' `sxc`i'', msymbol(|) mcolor(black) msize(medium))"'
				local spellip "`spellip' `sgraph`i'' `xplot2'"
			}

			#delimit;
			graph twoway `spellip' (scatteri `maxx' 0.5 (3) "{bf: `gtitle2'}", ms(i) mlabc(black) mlabsize(*0.85)),
				ytitle("") xtitle("") fxsize(20) yscale(off) legend(off) plotregion(style(none))
				title("") xlab(0(0.25)1) graphregion(style(none)) nodraw name(`ellip2', replace);
			#delimit cr

			#delimit;
			nois graph combine `forplot' `graphlist' `ellip1' `forplot1' `ellip2' `forplot2', rows(1) ycommon iscale(`combscale');
			#delimit cr
		}
		else if (strpos("`plottype'", "generic") != 0) {

			forvalues k = 1/`max' {
				local `sens'`k' = `sens' in `k'
				local `senslo'`k' = `senslo' in `k'
				local `senshi'`k' = `senshi' in `k'
				local plot`k' `"(pci `k' ``senslo'`k'' `k' ``senshi'`k'', lcolor(blue) lwidth(medium) lpat(solid))(scatteri `k' ``sens'`k'', msymbol(S) mcolor(black) msize(*1.0))"'
				local ggeneric1 "`ggeneric1' `plot`k''"
			}

			#delimit;
			graph twoway `ggeneric1' (scatteri `maxx' 0.5 (3) "{bf: `gtitle1'}", ms(i) mlabc(black) mlabsize(*0.85)),
				ytitle("") xtitle("") yscale(off) xlab(0(0.5)1.0) fxsize(20) ylab(none)
				legend(off) plotregion(style(none)) nodraw graphregion(style(none)) name(`generic1', replace);
			#delimit cr

			forvalues k = 1/`max' {
				local `spec'`k' = `spec' in `k'
				local `speclo'`k' = `speclo' in `k'
				local `spechi'`k' = `spechi' in `k'
				local plot`k' `"(pci `k' ``speclo'`k'' `k' ``spechi'`k'', lcolor(blue) lwidth(medium) lpat(solid))(scatteri `k' ``spec'`k'', msymbol(S) mcolor(black) msize(*1.0))"'
				local ggeneric2 "`ggeneric2' `plot`k''"
			}

			#delimit;
			graph twoway `ggeneric2' (scatteri `maxx' 0.5 (3) "{bf: `gtitle2'}", ms(i) mlabc(black) mlabsize(*0.85)),
				ytitle("") xtitle("") fxsize(20) yscale(off) xlab(0(0.5)1.0)
				legend(off) nodraw plotregion(style(none)) graphregion(style(none)) name(`generic2', replace);
			#delimit cr

			* Use taller graph to give rows more space
			local _ysize = max(8, `max' * 0.28)
			#delimit;
			nois graph combine `forplot' `graphlist' `generic1' `forplot1' `generic2' `forplot2', rows(1) ycommon  iscale(`combscale')
				ysize(`_ysize') xsize(18);
			#delimit cr
		}
		else if (strpos("`plottype'", "thick") != 0) {
			forvalues k = 1/`max' {
				local `sens'`k' = `sens' in `k'
				local `senslo'`k' = `senslo' in `k'
				local `senshi'`k' = `senshi' in `k'
				local plot11`k' `"(pci `k' ``senslo'`k'' `k' ``senshi'`k'', lcol(blue) lpat(solid) lwidth(medium))"'
				local plot12`k' `"(scatteri `k' ``sens'`k'', msymbol(|) mcolor(black) msize(medium))"'
				local gthick1 `"`gthick1' `plot11`k'' `plot12`k''"'
			}

			#delimit;
			graph twoway `gthick1' (scatteri `maxx' 0.5 (3) "{bf: `gtitle1'}", ms(i) mlabc(black) mlabsize(*0.85)),
				ytitle("") xtitle("") fxsize(20) yscale(off) xlab(0(0.5)1.0) legend(off) nodraw
				plotregion(style(none)) graphregion(style(none)) name(`thick1', replace);
			#delimit cr

			forvalues k = 1/`max' {
				local `spec'`k' = `spec' in `k'
				local `speclo'`k' = `speclo' in `k'
				local `spechi'`k' = `spechi' in `k'
				local plot21`k' `"(pci `k' ``speclo'`k'' `k' ``spechi'`k'', lwidth(medium) lcol(blue) lpat(solid))"'
				local plot22`k' `"(scatteri `k' ``spec'`k'', msymbol(|) mcolor(black) msize(medium))"'
				local gthick2 `"`gthick2' `plot21`k'' `plot22`k''"'
			}

			#delimit;
			graph twoway `gthick2' (scatteri `maxx' 0.5 (3) "{bf: `gtitle2'}", ms(i) mlabc(black) mlabsize(*0.85)),
				ytitle("") xtitle("") fxsize(20) yscale(off) xlab(0(0.5)1.0)
				legend(off) nodraw plotregion(style(none)) graphregion(style(none)) name(`thick2', replace);
			#delimit cr

			#delimit;
			nois graph combine `forplot' `graphlist' `thick1' `forplot1' `thick2' `forplot2', rows(1) ycommon iscale(`combscale');
			#delimit cr
		}
		else if (strpos("`plottype'", "rain") != 0) {
			local npoints = 1000
			forvalues k = 1/`max' {
				tempvar x yupper ylower
				local mu`k' = `sens' in `k'
				local musd`k' = `sensse' in `k'
				local mulow`k' = `senslo' in `k'
				local muhi`k' = `senshi' in `k'
				local tp`k' = tp in `k'
				local fn`k' = fn in `k'
				qui range `x'`k' `mulow`k'' `muhi`k'' `npoints'
				qui gen double `yupper'`k' = `tp`k'' * log(`x'`k') + `fn`k'' * log(1 - `x'`k')
				qui summ `yupper'`k'
				local ymax = r(min)
				qui replace `yupper'`k' = (`yupper'`k' - `ymax') * 0.1
				qui gen double `ylower'`k' = -`yupper'`k'
				qui replace `yupper'`k' = `yupper'`k' + `k'
				qui replace `ylower'`k' = `ylower'`k' + `k'
				qui summ `x'`k'
				local xmin`k' = r(min)
				local xmax`k' = r(max)
				local xmean`k' = r(mean)

				local rainplot11`k' `"(rarea `ylower'`k' `yupper'`k' `x'`k', lwidth(none) fcolor(blue))"'
				local rainplot12`k' `"(pci `k' `xmin`k'' `k' `xmax`k'', lcolor(black) lwidth(thin) lpat(solid))"'
				local rainplot13`k' `"(scatteri `k' `xmean`k'', msymbol(|) mcolor(black) msize(medium))"'
				local rainplot`k' `"`rainplot11`k'' `rainplot12`k'' `rainplot13`k''"'
				local grain1 "`grain1' `rainplot`k''"
			}

			#delimit;
			graph twoway `grain1' (scatteri `maxx' 0.5 (3) "{bf: `gtitle1'}", ms(i) mlabc(black) mlabsize(*0.85)),
				ytitle("") xtitle("") fxsize(20) yscale(off) legend(off) xlab(0(0.5)1.0) nodraw
				plotregion(style(none)) graphregion(style(none)) name(`rain1', replace);
			#delimit cr

			forvalues k = 1/`max' {
				tempvar x yupper ylower
				local mu`k' = `spec' in `k'
				local musd`k' = `specse' in `k'
				local mulow`k' = `speclo' in `k'
				local muhi`k' = `spechi' in `k'
				local fp`k' = fp in `k'
				local tn`k' = tn in `k'
				qui range `x'`k' `mulow`k'' `muhi`k'' `npoints'
				qui gen double `yupper'`k' = `tn`k'' * log(`x'`k') + `fp`k'' * log(1 - `x'`k')
				qui summ `yupper'`k'
				local ymax = r(min)
				qui replace `yupper'`k' = (`yupper'`k' - `ymax') * 0.1
				qui gen double `ylower'`k' = -`yupper'`k'
				qui replace `yupper'`k' = `yupper'`k' + `k'
				qui replace `ylower'`k' = `ylower'`k' + `k'
				qui summ `x'`k'
				local xmin`k' = r(min)
				local xmax`k' = r(max)
				local xmean`k' = r(mean)
				local rainplot21`k' `"(rarea `ylower'`k' `yupper'`k' `x'`k', lwidth(none) fcolor(blue))"'
				local rainplot22`k' `"(pci `k' `xmin`k'' `k' `xmax`k'', lcolor(black) lwidth(thin) lpat(solid))"'
				local rainplot23`k' `"(scatteri `k' `xmean`k'', msymbol(|) mcolor(black) msize(medium))"'
				local rainplot`k' `"`rainplot21`k'' `rainplot22`k'' `rainplot23`k''"'
				local grain2 "`grain2' `rainplot`k''"
			}

			#delimit;
			graph twoway `grain2' (scatteri `maxx' 0.5 (3) "{bf: `gtitle2'}", ms(i) mlabc(black) mlabsize(*0.85)),
				ytitle("") xtitle("") fxsize(20) yscale(off) legend(off) xlab(0(0.5)1.0) nodraw
				plotregion(style(none)) graphregion(style(none)) name(`rain2', replace);
			#delimit cr

			#delimit;
			nois graph combine `forplot' `graphlist' `rain1' `forplot1' `rain2' `forplot2', rows(1) ycommon iscale(`combscale');
			#delimit cr
		}

		capture restore
	}

	capture estimates restore _midas_estimates
end

program define midas_cii
version 16

syntax varlist [if] [in], p(name) lowerci(name) upperci(name) [cimethod(string) level(real 95)]

qui {
		tokenize `varlist'
		gen `p' = .
		gen `lowerci' = .
		gen `upperci' = .

		count `if' `in'
		forvalues i = 1/`r(N)' {
		local N = `1'[`i']
		local n = `2'[`i']
		if `N' ==0 & `n' ==0 {
		replace `p' = 0 in `i'
		replace `lowerci' = 0 in `i'
		replace `upperci' = 0 in `i'
		}
		else {
		cii proportions `N' `n', `cimethod' level(`level')

		replace `p' = r(proportion) in `i'
		replace `lowerci' = r(lb) in `i'
		replace `upperci' = r(ub) in `i'
			}
		}
}
end