R/studentGrowthPlot.R

Defines functions `studentGrowthPlot`

`studentGrowthPlot` <-
function(Scale_Scores,                    ## Vector of Scale Scores
	Plotting_Scale_Scores,                ## Score used for plotting, if missing, then Scale_Scores are used
                                          ## for plotting, if supplied Scale_Scores used for text
	Achievement_Levels,                   ## NOTE: Achievement_Levels must/should be supplied as factors with appropriate level codings
	SGP,                                  ## Vector of SGPs
	SGP_Levels,                           ## Vector of SGP Levels
	Grades,                               ## Vector of Grade levels for student
	Content_Areas,                        ## Vector of Content Areas for student
	Cuts,                                 ## Vector of NY1, NY2, and NY3 cutscores
	Plotting_Cuts,                        ## Vector of NY1, NY2, and NY3 cutscores used for plotting (transformed if non-vertical/equated scale)
	SGP_Targets,                          ## Vector of CUKU, CUKU_Current, MUSU, MUSU_Current (multi) year targets
	SGP_Scale_Score_Targets,              ## Vector of CUKU, CUKU_Current, MUSU, MUSU_Current scale score targets
	Plotting_SGP_Scale_Score_Targets,     ## Vector of CUKU, CUKU_Current, MUSU, MUSU_Current scale score targets for plotting (transformed if non-vertical/equated scale)
	Cutscores,                            ## data.frame of long formatted achievement level cutscores
	Years,                                ## Vector of years corresponding to Scale_Scores, Content_Areas, ... arguments supplied
	Report_Parameters) {                  ## list containing Current_Year, Content_Area, Content_Area_Title, State, Denote_Content_Area, SGP_Targets,
                                          ## Configuration, Language, Assessment_Transition, Fan

	############################################
	### Create relevant variables
	############################################

	YEAR <- CONTENT_AREA <- GRADE <- GRADE_NUMERIC <- CUTSCORES <- CUTLEVEL <- level_1_1_curve <- level_2_1_curve <- NULL ## To prevent R CMD check warnings
	CUTSCORES_EQUATED <- CUTSCORES_TRANSFORMED <- NULL

	number.growth.levels <- length(SGP::SGPstateData[[Report_Parameters$State]][["Growth"]][["Levels"]])
	if (!is.null(SGP::SGPstateData[[Report_Parameters$State]][["Student_Report_Information"]][["Growth_Level_Labels"]])) {
		growth.level.labels <- as.character(unlist(SGP::SGPstateData[[Report_Parameters$State]][["Student_Report_Information"]][["Growth_Level_Labels"]]))
		growth.level.labels.shortened <- names(SGP::SGPstateData[[Report_Parameters$State]][["Student_Report_Information"]][["Growth_Level_Labels"]])
	} else {
		growth.level.labels <- growth.level.labels.shortened <- SGP::SGPstateData[[Report_Parameters$State]][["Growth"]][["Levels"]]
	}
	growth.level.cutscores <- SGP::SGPstateData[[Report_Parameters$State]][["Growth"]][["Cutscores"]][["Cuts"]]
	growth.level.cutscores.text <- SGP::SGPstateData[[Report_Parameters$State]][["Growth"]][["Cutscores"]][["Labels"]]
	content.area.label <- SGP::SGPstateData[[Report_Parameters$State]][["Student_Report_Information"]][["Content_Areas_Labels"]][[Report_Parameters$Content_Area_Title]]

	if (!is.null(SGP::SGPstateData[[Report_Parameters$State]][["SGP_Configuration"]][["content_area.projection.sequence"]][[Report_Parameters$Content_Area]])) {
		grades.content_areas.reported.in.state <- data.frame(
			GRADE=SGP::SGPstateData[[Report_Parameters$State]][["SGP_Configuration"]][["grade.projection.sequence"]][[Report_Parameters$Content_Area]],
			YEAR_LAG=c(1, SGP::SGPstateData[[Report_Parameters$State]][["SGP_Configuration"]][["year_lags.projection.sequence"]][[Report_Parameters$Content_Area]]),
			CONTENT_AREA=SGP::SGPstateData[[Report_Parameters$State]][["SGP_Configuration"]][["content_area.projection.sequence"]][[Report_Parameters$Content_Area]],
			stringsAsFactors=FALSE
		)
	} else {
		grades.content_areas.reported.in.state <- data.frame(
			GRADE=SGP::SGPstateData[[Report_Parameters$State]][["Student_Report_Information"]][["Grades_Reported"]][[Report_Parameters$Content_Area]],
			YEAR_LAG=c(1, diff(as.numeric(SGP::SGPstateData[[Report_Parameters$State]][["Student_Report_Information"]][["Grades_Reported"]][[Report_Parameters$Content_Area]]))),
			CONTENT_AREA=Report_Parameters$Content_Area,
			stringsAsFactors=FALSE
		)
	}

	grades.content_areas.reported.in.state <- subset(grades.content_areas.reported.in.state, !(is.na(GRADE) & is.na(CONTENT_AREA)))
	grades.content_areas.reported.in.state$GRADE_NUMERIC <- (as.numeric(grades.content_areas.reported.in.state$GRADE[2])-1)+c(0, cumsum(tail(grades.content_areas.reported.in.state$YEAR_LAG, -1)))

	grade.rpt.index <- intersect(grep(paste0("^", Grades[1], "$"), grades.content_areas.reported.in.state$GRADE), grep(paste0("^", Content_Areas[1], "$"), grades.content_areas.reported.in.state$CONTENT_AREA)) #  needed when transition grade is the same - e.g. Grade 9 ELA to Grade 9 PSAT (not 9 to EOCT)
	if (length(grade.rpt.index)==0) {
		if (max(Grades, na.rm = TRUE) == "EOCT") {
			grade.rpt.index <- min(grep(max(Grades, na.rm = TRUE), grades.content_areas.reported.in.state$GRADE))
		} else	grade.rpt.index <- min(nrow(grades.content_areas.reported.in.state), (max(grep(max(Grades, na.rm = TRUE), grades.content_areas.reported.in.state$GRADE)) + min(which(!is.na(Grades))) - 1))
	}
	grades.content_areas.reported.in.state$YEAR <- Report_Parameters$Current_Year
	for (y in (grade.rpt.index-1):1) grades.content_areas.reported.in.state$YEAR[y] <- yearIncrement(grades.content_areas.reported.in.state$YEAR[y+1], 0, grades.content_areas.reported.in.state$YEAR_LAG[y+1])
	if (grade.rpt.index != nrow(grades.content_areas.reported.in.state)) {
		for (y in (grade.rpt.index+1):max((grade.rpt.index+1),(nrow(grades.content_areas.reported.in.state)))) { # -1))) {
			grades.content_areas.reported.in.state$YEAR[y] <-
				yearIncrement(grades.content_areas.reported.in.state$YEAR[y-1], 0, -grades.content_areas.reported.in.state$YEAR_LAG[y]) # YEAR_LAG[y] not YEAR_LAG[y-1] because YEAR_LAG= above is offset by 1  w/ c(1, SGP::...)
		}
	}

	if (!is.null(SGP::SGPstateData[[Report_Parameters$State]][["Student_Report_Information"]][["Earliest_Year_Reported"]])) {
		grade.rpt.tf <- sapply(1:nrow(grades.content_areas.reported.in.state), function(y) grades.content_areas.reported.in.state$YEAR[y] >=
			SGP::SGPstateData[[Report_Parameters$State]][["Student_Report_Information"]][["Earliest_Year_Reported"]][[grades.content_areas.reported.in.state$CONTENT_AREA[y]]])
		if (!all(grade.rpt.tf))	{
			grades.content_areas.reported.in.state <- grades.content_areas.reported.in.state[-which(!grade.rpt.tf),]
		}
	}

	if (!is.null(transition.grade <- SGP::SGPstateData[[Report_Parameters$State]][["Assessment_Program_Information"]][["Assessment_Transition"]][["Grade"]])) {
		post.trans.content_areas <- names(SGP::SGPstateData[[Report_Parameters$State]][["Assessment_Program_Information"]][["Assessment_Transition"]][[paste0('Content_Areas_Labels.', transition.grade)]])
		transition.grade.numeric <- suppressWarnings(min(as.data.table(grades.content_areas.reported.in.state)[as.numeric(GRADE) >= as.numeric(transition.grade) & CONTENT_AREA %in% post.trans.content_areas, GRADE_NUMERIC]))
		grades.content_areas.reported.in.state <- grades.content_areas.reported.in.state[union(sort(c(
			which(grades.content_areas.reported.in.state$CONTENT_AREA %in% c(Content_Areas, post.trans.content_areas)))),
			max(grep(paste0("^", Grades[!is.na(Grades)][1], "$"), grades.content_areas.reported.in.state$GRADE)):nrow(grades.content_areas.reported.in.state)),]
		max.year.index <- max(grep(Report_Parameters$Current_Year, grades.content_areas.reported.in.state$YEAR))
		if (transition.grade.numeric %in% grades.content_areas.reported.in.state$GRADE_NUMERIC[1:max.year.index]) {
			tmp.gcar <- subset(grades.content_areas.reported.in.state,
				!(YEAR >= Report_Parameters$Current_Year & !CONTENT_AREA %in% post.trans.content_areas))
			if (all(Content_Areas[!is.na(Grades)] %in% tmp.gcar$CONTENT_AREA)) {
				grades.content_areas.reported.in.state <- tmp.gcar
				if (!all(diff(grades.content_areas.reported.in.state$GRADE_NUMERIC)==1)) {
					grades.content_areas.reported.in.state$GRADE_NUMERIC <- (as.numeric(grades.content_areas.reported.in.state$GRADE_NUMERIC[2])-1)+c(0, cumsum(tail(grades.content_areas.reported.in.state$YEAR_LAG, -1)))
				}
			} else {
				if (Grades[!is.na(Grades)][1] == transition.grade) {
					grd.trans.index <- intersect(grep(paste0("^", Grades[!is.na(Grades)][1], "$"), grades.content_areas.reported.in.state$GRADE), grep(paste0("^", Content_Areas[1], "$"), grades.content_areas.reported.in.state$CONTENT_AREA)) #  needed when transition grade is the same - e.g. Grade 9 ELA to Grade 9 PSAT (not 9 to EOCT)
					end <- nrow(grades.content_areas.reported.in.state)
					grades.content_areas.reported.in.state$YEAR[grd.trans.index:1] <- as.numeric(grades.content_areas.reported.in.state$YEAR[grd.trans.index]) - c(0, cumsum(rep(1, length(grades.content_areas.reported.in.state$YEAR_LAG[grd.trans.index:1])-1)))
					grades.content_areas.reported.in.state$GRADE_NUMERIC[grd.trans.index:end] <- grades.content_areas.reported.in.state$GRADE_NUMERIC[grd.trans.index] + cumsum(c(1, tail(grades.content_areas.reported.in.state$YEAR_LAG[grd.trans.index:end], -1)))
				}
			}
		}
	}

	test.abbreviation <- SGP::SGPstateData[[Report_Parameters$State]][["Assessment_Program_Information"]][["Assessment_Abbreviation"]]

	if (identical(SGP::SGPstateData[[Report_Parameters$State]][['Assessment_Program_Information']][['Test_Season']], "Fall")) {
		test.season <- SGP::SGPstateData[[Report_Parameters$State]][['Assessment_Program_Information']][['Test_Season']]
	} else {
		test.season <- NULL
	}

	if (!is.null(SGP::SGPstateData[[Report_Parameters$State]][["Student_Report_Information"]][["Use_EOCT_Label"]])) {
		print.eoct.content.area <- FALSE
	} else print.eoct.content.area <- TRUE

	border.color <- "grey25"
	if (is.null(SGP::SGPstateData[[Report_Parameters$State]][["SGP_Configuration"]][["arrow.legend.color"]])) {
		arrow.legend.color <- rev(diverge_hcl(number.growth.levels, h = c(180, 40), c = 255, l = c(20, 100)))
	} else {
		arrow.legend.color <- SGP::SGPstateData[[Report_Parameters$State]][["SGP_Configuration"]][["arrow.legend.color"]]
	}
	if (is.null(SGP::SGPstateData[[Report_Parameters$State]][["SGP_Configuration"]][["sgp.target.types"]])) {
		sgp.target.types <- c("Scale_Score_Targets_CUKU", "Scale_Score_Targets_MUSU", "Scale_Score_Targets_Current_CUKU", "Scale_Score_Targets_Current_MUSU")
	} else {
		sgp.target.types <- SGP::SGPstateData[[Report_Parameters$State]][["SGP_Configuration"]][["sgp.target.types"]]
	}
	missing.data.symbol <- "--"
	if (!is.null(my.year.span <- SGP::SGPstateData[[Report_Parameters$State]][["Student_Report_Information"]][["sgPlot.year.span"]])) {
		studentGrowthPlot.year.span <- my.year.span
	} else studentGrowthPlot.year.span <- 5
	if (is.null(Report_Parameters[['Denote_Content_Area']]) || Report_Parameters[['Denote_Content_Area']]==FALSE) {
		legend.fill.color <- "white"
	} else {
		legend.fill.color <- rgb(0,0,1,0.25)
	}

	if (is.null(Report_Parameters[['Configuration']][['Connect_Points']])) {
		connect.points <- "Arrows"
	} else {
		connect.points <- Report_Parameters[['Configuration']][['Connect_Points']]
	}

	if (!is.null(Report_Parameters[['SGP_Targets']])) {
		if (all(c("sgp.projections", "sgp.projections.lagged") %in% Report_Parameters[['SGP_Targets']]) | all(c("sgp.projections.baseline", "sgp.projections.lagged.baseline") %in% Report_Parameters[['SGP_Targets']])) tmp.target.types <- names(unlist(SGP_Targets)[!is.na(unlist(SGP_Targets))])
		if (identical("sgp.projections", Report_Parameters[['SGP_Targets']]) | identical("sgp.projections.baseline", Report_Parameters[['SGP_Targets']])) tmp.target.types <- grep("Current", names(unlist(SGP_Targets)[!is.na(unlist(SGP_Targets))]), value=TRUE)
		if (identical("sgp.projections.lagged", Report_Parameters[['SGP_Targets']]) | identical("sgp.projections.lagged.baseline", Report_Parameters[['SGP_Targets']])) tmp.target.types <- grep("Current", names(unlist(SGP_Targets)[!is.na(unlist(SGP_Targets))]), value=TRUE, invert=TRUE)
		if (identical("CUSTOM", Report_Parameters[['SGP_Targets']])) tmp.target.types <- grep("CUSTOM", names(unlist(SGP_Targets)[!is.na(unlist(SGP_Targets))]), value=TRUE)
	}

	if (!is.null(SGP::SGPstateData[[Report_Parameters$State]][['SGP_Configuration']][['sgPlot.show.content_area.progression']]) |
		!is.null(Report_Parameters[['Configuration']][['Assessment_Transition']])) {
			sgPlot.show.content_area.progression <- SGP::SGPstateData[[Report_Parameters$State]][['SGP_Configuration']][['sgPlot.show.content_area.progression']]
	} else {
		if (is.null(Report_Parameters[['Configuration']][['Show_Content_Area_Progression']])) {
			sgPlot.show.content_area.progression <- FALSE
		} else {
			sgPlot.show.content_area.progression <- Report_Parameters[['Configuration']][['Show_Content_Area_Progression']]
		}
	}

	if (sgPlot.show.content_area.progression) print.eoct.content.area <- FALSE else print.eoct.content.area <- TRUE

	if (is.null(SGP::SGPstateData[[Report_Parameters$State]][['SGP_Configuration']][['Show_Fan_Cut_Scores']])) {
		show.fan.cutscores <- FALSE
	} else {
		show.fan.cutscores <- TRUE
	}

	if (is.null(SGP::SGPstateData[[Report_Parameters$State]][['SGP_Configuration']][['Show_Fan_Growth_Labels']])) {
		show.fan.growth.labels <- TRUE
	} else {
		show.fan.growth.labels <- FALSE
	}

	if (is.null(Report_Parameters[['Configuration']][['Font_Size']])) {
		title.ca.size <- 1.8
		legend.size <- 0.5
		legend.lheight <- 1
		left.vp.size <- 0.85
		bottom.right.vp.size <- 1.2
		bottom.left.vp.size <- 0.7
	} else {
		if (!is.null(Report_Parameters[['Configuration']][['Font_Size']][['title.ca.size']])) {
			title.ca.size <- Report_Parameters[["Configuration"]][["Font_Size"]][["title.ca.size"]]
		} else title.ca.size <- 1.8
		if (!is.null(Report_Parameters[['Configuration']][['Font_Size']][['legend.size']])) {
			legend.size <- Report_Parameters[['Configuration']][['Font_Size']][['legend.size']]
		} else legend.size <- 0.5
		if (!is.null(Report_Parameters[['Configuration']][['Font_Size']][['legend.lheight']])) {
			legend.lheight <- Report_Parameters[['Configuration']][['Font_Size']][['legend.lheight']]
		} else legend.lheight <- 1
		if (!is.null(Report_Parameters[['Configuration']][['Font_Size']][['left.vp.size']])) {
			left.vp.size <- Report_Parameters[['Configuration']][['Font_Size']][['left.vp.size']]
		} else left.vp.size <- 0.85
		if (!is.null(Report_Parameters[['Configuration']][['Font_Size']][['bottom.right.vp.size']])) {
			bottom.right.vp.size <- Report_Parameters[["Configuration"]][["Font_Size"]][["bottom.right.vp.size"]]
		} else bottom.right.vp.size <- 1.2
		if (!is.null(Report_Parameters[['Configuration']][['Font_Size']][['bottom.left.vp.size']])) {
			bottom.left.vp.size <- Report_Parameters[['Configuration']][['Font_Size']][['bottom.left.vp.size']]
		} else bottom.left.vp.size <- 0.7
	}

	if (!is.null(Report_Parameters[['Configuration']][['Font_Family']])) {
		font.fam <- Report_Parameters[['Configuration']][['Font_Family']]
	} else font.fam <- "Helvetica-Narrow"

	if (is.null(Report_Parameters[['Configuration']][['Language']])) {
		achievement.label <- "Achievement"
		achievement_level.label <- "Achievement Level"
		achievement_target.label <- "Achievement Target"
		growth.label <- "Growth"
		growth_percentile.label <- "Growth Percentile"
		growth_level.label <- "Growth Level"
		growth_target.label <- "Growth Target"
		level.label <- "Level"
		percentiles.label <- "Percentiles"
		scale_score.label <- "Scale Score"
		grade.label <- "Grade"
		CU.label <- "Catch Up"
		KU.label <- "Keep Up"
		MU.label <- "Move Up"
		SU.label <- "Stay Up"
		target.label <- "Target"
	}

	if (identical(toupper(Report_Parameters[['Configuration']][['Language']]), "SPANISH")) { # WIDA_CO and NH
		achievement.label <- "Resultado"
		achievement_level.label <- "Nivel de Capacitaci\uF3n" # unicode for accented o is \uF3 - http://www.ascii.cl/htmlcodes.htm
		achievement_target.label <- "Meta de Capacitaci\uF3n"
		growth.label <- "Crecimiento"
		growth_percentile.label <- "Porcentaje de Crecimiento"
		growth_level.label <- "Nivel de Crecimiento"
		growth_target.label <- "Meta de Crecimiento"
		level.label <- "Niveles"
		percentiles.label <- "El Percentil"
		scale_score.label <- "Escala"
		grade.label <- "Grado"
		CU.label <- "Alcanzar"
		KU.label <- "Guadar"
		MU.label <- "Avanzar"
		SU.label <- "Mantener"
		target.label <- "Meta"
		SGP_Levels <- SGP::SGPstateData[[Report_Parameters$State]][["Growth"]][["Levels"]][match(SGP_Levels, SGP::SGPstateData[[paste(head(unlist(strsplit(Report_Parameters$State, "_")), -1), collapse="_")]][["Growth"]][["Levels"]])]
	}

	if (is.list(Report_Parameters[["Configuration"]][["Language"]])) { # See WIDA_GA for example
  		achievement.label <- Report_Parameters[["Configuration"]][["Language"]][["Custom"]][["achievement.label"]]
		achievement_level.label <- Report_Parameters[["Configuration"]][["Language"]][["Custom"]][["achievement_level.label"]]
		achievement_target.label <- Report_Parameters[["Configuration"]][["Language"]][["Custom"]][["achievement_target.label"]]
		growth.label <- Report_Parameters[["Configuration"]][["Language"]][["Custom"]][["growth.label"]]
		growth_percentile.label <- Report_Parameters[["Configuration"]][["Language"]][["Custom"]][["growth_percentile.label"]]
		growth_level.label <- Report_Parameters[["Configuration"]][["Language"]][["Custom"]][["growth_level.label"]]
		growth_target.label <- Report_Parameters[["Configuration"]][["Language"]][["Custom"]][["growth_target.label"]]
		level.label <- Report_Parameters[["Configuration"]][["Language"]][["Custom"]][["level.label"]]
		percentiles.label <- Report_Parameters[["Configuration"]][["Language"]][["Custom"]][["percentiles.label"]]
		scale_score.label <- Report_Parameters[["Configuration"]][["Language"]][["Custom"]][["scale_score.label"]]
		grade.label <- Report_Parameters[["Configuration"]][["Language"]][["Custom"]][["grade.label"]]
		CU.label <- Report_Parameters[["Configuration"]][["Language"]][["Custom"]][["CU.label"]]
		KU.label <- Report_Parameters[["Configuration"]][["Language"]][["Custom"]][["KU.label"]]
		MU.label <- Report_Parameters[["Configuration"]][["Language"]][["Custom"]][["MU.label"]]
		SU.label <- Report_Parameters[["Configuration"]][["Language"]][["Custom"]][["SU.label"]]
		target.label <- Report_Parameters[["Configuration"]][["Language"]][["Custom"]][["target.label"]]
		SGP_Levels <- SGP::SGPstateData[[Report_Parameters$State]][["Growth"]][["Levels"]][match(SGP_Levels, SGP::SGPstateData[[paste(head(unlist(strsplit(Report_Parameters$State, "_")), -1), collapse = "_")]][["Growth"]][["Levels"]])]
	}

	if (!is.null(SGP::SGPstateData[[Report_Parameters$State]][["Student_Report_Information"]][["Content_Area_in_Legend"]])) {
		content.area.in.legend <- SGP::SGPstateData[[Report_Parameters$State]][["Student_Report_Information"]][["Content_Area_in_Legend"]]
	} else content.area.in.legend <- TRUE

	if (!is.null(SGP::SGPstateData[[Report_Parameters$State]][["Student_Report_Information"]][["Legend_Extra"]])) {
		extra.legend.element <- TRUE
		extra.legend.elements <- SGP::SGPstateData[[Report_Parameters$State]][["Student_Report_Information"]][["Legend_Extra"]]
	} else extra.legend.element <- FALSE
 
	if (is.null(Report_Parameters[['Fan']])) {
		show.fan <- TRUE
	} else {
		show.fan <- eval(parse(text=Report_Parameters[['Fan']]))
	}

	if (!is.null(SGP::SGPstateData[[Report_Parameters$State]][["Student_Report_Information"]][["Fan_Extra"]])) {
		fan.extra <- TRUE
		fan.extras <- SGP::SGPstateData[[Report_Parameters$State]][["Student_Report_Information"]][["Fan_Extra"]]
	} else fan.extra <- FALSE

	Cutscores <- copy(Cutscores)

	if (!is.null(Report_Parameters[['Assessment_Transition']])) Cutscores[,CUTSCORES:=CUTSCORES_EQUATED]
	if (is.null(Report_Parameters[['Assessment_Transition']]) &&
		Report_Parameters$Current_Year %in% SGP::SGPstateData[[Report_Parameters$State]][["Student_Report_Information"]][["Transformed_Achievement_Level_Cutscores"]][[Report_Parameters$Content_Area_Title]]) {
		Cutscores[,CUTSCORES:=CUTSCORES_TRANSFORMED]
	}


	##############################
	### Utility functions
	##############################

	`%w/o%` <- function(x, y) x[!x %in% y]

	ach.level.labels <- function(perlevel){
		tmp <- unlist(sapply(achievement.level.labels, names), use.names=FALSE)[match(perlevel, unlist(achievement.level.labels))]
		tmp[is.na(tmp) & !is.na(perlevel)] <- perlevel[is.na(tmp) & !is.na(perlevel)]
		tmp[is.na(tmp)] <- missing.data.symbol
		return(tmp)
	}

	sgp.level.labels <- function(sgp_level){
		sgp_level[is.na(sgp_level)] <- missing.data.symbol
		return(sgp_level)
	}

	arrow.color <- function(sgp){
		arrow.legend.color[findInterval(sgp, growth.level.cutscores)+1]
	}

	get.my.cutscore.year <- function(state, content_area, year, cutlevel) {
		year <- tail(sort(c(SGP::SGPstateData[[state]][["Student_Report_Information"]][["Earliest_Year_Reported"]][[content_area]], year)), 1)
		tmp.index <- grep(content_area, names(SGP::SGPstateData[[state]][["Achievement"]][["Cutscores"]]))
		tmp.cutlevels <- sapply(tmp.index, function(x) length(SGP::SGPstateData[[state]][["Achievement"]][["Cutscores"]][[x]][[1]]))
		tmp.cutscore.years <- unique(sapply(strsplit(names(SGP::SGPstateData[[state]][["Achievement"]][["Cutscores"]])[tmp.index], "[.]"), '[', 2))
		tmp.cutscore.years <- tmp.cutscore.years[cutlevel <= tmp.cutlevels]
		if (year %in% tmp.cutscore.years) {
			return(year)
		} else {
			if (year==sort(c(year, tmp.cutscore.years))[1]) {
				return(NA)
			} else {
				return(sort(tmp.cutscore.years)[which(year==sort(c(year, tmp.cutscore.years)))-1])
			}
		}
	}

	interpolate.grades <- function(grades, content_areas, data.year.span, scale_scores) {
		last.number <- function (x) {
			if (sum(!is.na(x)) > 0) return(max(which(!is.na(x)))) else return (0)
		}

		first.number <- function (x) {
			if (sum(!is.na(x)) > 0 ) return(min(which(!is.na(x)))) else return (0)
		}

		convert.grades <- function(grades, content_areas, to="GRADE_NUMERIC", lookup=grades.content_areas.reported.in.state) {
			if (to=="GRADE_NUMERIC") {
				return(as.numeric(lookup$GRADE_NUMERIC[match(paste(grades, content_areas), paste(lookup$GRADE, lookup$CONTENT_AREA))]))
			}
			if (to=="GRADE") {
				return(as.character(lookup$GRADE[match(grades, lookup$GRADE_NUMERIC)]))
			}
		}

		extend.grades <- function (x, content_areas) {
			tmp.grades.numeric <- x
			tmp.content_areas <- grades.content_areas.reported.in.state$CONTENT_AREA[match(x, grades.content_areas.reported.in.state$GRADE_NUMERIC)]
			if ((length(tmp.content_areas) > length(x)) & any(duplicated(grades.content_areas.reported.in.state$GRADE_NUMERIC))) {
				tmp.dups <- grades.content_areas.reported.in.state$CONTENT_AREA[duplicated(grades.content_areas.reported.in.state$GRADE_NUMERIC)]
				tmp.content_areas <- tmp.content_areas[!tmp.content_areas %in% tmp.dups]
			}
			tmp.grades <- convert.grades(x, to="GRADE")
			tmp.head <- min(which(x[!is.na(x)][1] <= grades.content_areas.reported.in.state$GRADE_NUMERIC), na.rm=TRUE)
			tmp.tail <- min(which(tail(x[!is.na(x)], 1) <= grades.content_areas.reported.in.state$GRADE_NUMERIC), na.rm=TRUE)
			if (tmp.head==1) {
				tmp.grades <- c("GRADE_LOWER", tmp.grades); tmp.content_areas <- c("PLACEHOLDER", tmp.content_areas); tmp.grades.numeric <- c(NA, tmp.grades.numeric)
			} else {
				tmp.grades <- c(grades.content_areas.reported.in.state$GRADE[tmp.head-1], tmp.grades)
				tmp.content_areas <- c(grades.content_areas.reported.in.state$CONTENT_AREA[tmp.head-1], tmp.content_areas)
				tmp.grades.numeric <- c(grades.content_areas.reported.in.state$GRADE_NUMERIC[tmp.head-1], tmp.grades.numeric)
			}
			if (tmp.tail==dim(grades.content_areas.reported.in.state)[1]) {
				tmp.grades <- c(tmp.grades, "GRADE_UPPER");
				tmp.content_areas <- c(tmp.content_areas, "PLACEHOLDER");
				tmp.grades.numeric <- c(tmp.grades.numeric, NA)
			} else {
				tmp.grades <- c(tmp.grades, grades.content_areas.reported.in.state$GRADE[tmp.tail+1])
				tmp.content_areas <- c(tmp.content_areas, grades.content_areas.reported.in.state$CONTENT_AREA[tmp.tail+1])
				tmp.grades.numeric <- c(tmp.grades.numeric, grades.content_areas.reported.in.state$GRADE_NUMERIC[tmp.tail+1])
			}
			tmp.grades[is.na(tmp.grades)] <- tmp.grades.numeric[is.na(tmp.grades)]
			return(data.frame(GRADE=tmp.grades, GRADE_NUMERIC=tmp.grades.numeric, CONTENT_AREA=tmp.content_areas, stringsAsFactors=FALSE))
		}

		first.scale.score <- first.number(head(grades, data.year.span))
		last.scale.score <- last.number(grades)
		any.scale.scores <- any(!is.na(scale_scores))
		grades <- convert.grades(grades, content_areas)

		if (last.scale.score < data.year.span) {
			grades[(last.scale.score+1):data.year.span] <- (grades[last.scale.score]-1):(grades[last.scale.score] - (data.year.span - last.scale.score))
			grades[grades < min(grades.content_areas.reported.in.state$GRADE_NUMERIC)] <- min(grades.content_areas.reported.in.state$GRADE_NUMERIC)
		}

		if (first.scale.score > 1) {
			grades[1:(first.scale.score-1)] <- (grades[first.scale.score] + (first.scale.score - 1)):(grades[first.scale.score]+1)
			grades[grades > max(grades.content_areas.reported.in.state$GRADE_NUMERIC)] <- max(grades.content_areas.reported.in.state$GRADE_NUMERIC)
			if (any(is.na(grades))) {
				if (1 %in% which(is.na(grades))) grades[1] <- grades[min(which(!is.na(grades)))]+(min(which(!is.na(grades)))-1) # `approx` doesn't work when first value is NA
				if (length(grades) %in% which(is.na(grades))) grades[length(grades)] <- grades[min(which(!is.na(grades)))]-(length(grades)-min(which(!is.na(grades)))) # `approx` doesn't work when last value is NA
				grades[which(is.na(grades))] <- approx(grades, xout=which(is.na(grades)))$y
				grades <- as.integer(grades)
			}
			if (!grades[1] %in% grades.content_areas.reported.in.state$GRADE_NUMERIC) {
				grades[1] <- grades.content_areas.reported.in.state$GRADE_NUMERIC[which.min(grades[1] > grades.content_areas.reported.in.state$GRADE_NUMERIC)-1]
			}
			if (any(!grades %in% grades.content_areas.reported.in.state$GRADE_NUMERIC)) {
				for (tmp.missing.grades in which(!grades %in% grades.content_areas.reported.in.state$GRADE_NUMERIC)) {
					grades[tmp.missing.grades] <-
						grades.content_areas.reported.in.state$GRADE_NUMERIC[which.max(grades[tmp.missing.grades] < grades.content_areas.reported.in.state$GRADE_NUMERIC)]
				}
			}
		}

		if (any(is.na(grades))) {
			tmp.na <- which(is.na(grades))
			tmp.not.na <- max(which(!is.na(grades)))
			while (any(is.na(grades))) {
				nearest.not.na <- tmp.na[which.min(abs(tmp.na-tmp.not.na))]; tmp.dif <- (tmp.not.na-nearest.not.na)
				tmp.index <- match(grades[tmp.not.na], grades.content_areas.reported.in.state$GRADE_NUMERIC)+tmp.dif
				if (tmp.index <= 0L) tmp.index <- 1
				if (tmp.index > nrow(grades.content_areas.reported.in.state)) tmp.index <- nrow(grades.content_areas.reported.in.state)
				grades[tmp.not.na-tmp.dif] <- grades.content_areas.reported.in.state$GRADE_NUMERIC[tmp.index]
				tmp.na <- which(is.na(grades)); tmp.not.na <- min(which(!is.na(grades)))
			}
			if (!length(grades) %in% tmp.na && length(intersect(tmp.na, which(!is.na(grades)))) > 0) {
				tmp.indices <- intersect(tmp.na, which(!is.na(grades)))
				grades[tmp.indices] <- NA
				grades[tmp.indices] <- round(approx(grades, xout=tmp.indices)$y)
			}
		}

		if (grades[1]==max(grades.content_areas.reported.in.state$GRADE_NUMERIC)) {
			year_span <- data.year.span
			temp.grades.content_areas <- extend.grades(rev(grades), content_areas)
			return(list(
				interp.df = temp.grades.content_areas,
				year_span=year_span,
				any_scale_scores=any.scale.scores,
				increment_for_projection_current=0,
				years=yearIncrement(Report_Parameters$Current_Year, seq(1-max(which(grades[1]==temp.grades.content_areas$GRADE_NUMERIC)), length=dim(temp.grades.content_areas)[1]))))
		} else {
			year.increment.for.projection.current <- grades.content_areas.reported.in.state$YEAR_LAG[which(grades[1]==grades.content_areas.reported.in.state$GRADE_NUMERIC)+1]
			year_span <- max(min(last.scale.score, data.year.span-1), min(grades[1]-min(grades.content_areas.reported.in.state$GRADE_NUMERIC)+1, data.year.span-1)) - (year.increment.for.projection.current-1)
			temp.grades <- c(rev(head(grades, year_span)),
				head(seq(grades.content_areas.reported.in.state$GRADE_NUMERIC[match(grades[1], grades.content_areas.reported.in.state$GRADE_NUMERIC)]+1, length=data.year.span),
					data.year.span-year_span))
			temp.grades.content_areas <- extend.grades(temp.grades, content_areas)
			return(list(
				interp.df = temp.grades.content_areas,
				year_span=year_span,
				any_scale_scores=any.scale.scores,
				increment_for_projection_current=year.increment.for.projection.current,
				years=yearIncrement(Report_Parameters$Current_Year, seq(1-max(which(grades[1]==temp.grades.content_areas$GRADE_NUMERIC)), length=dim(temp.grades.content_areas)[1]))))
		}
	} ### END interpolate.grades function

	year.function <- function(year, add.sub, vec.length, output.type="numeric", season=NULL) {
		if (is.null(season)) {
			if (grepl("_", year)) {
				tmp <- as.numeric(unlist(strsplit(strsplit(as.character(year), "[.]")[[1]][1], "_")))+add.sub
				if (output.type=="numeric") {
					return(seq(from=tmp[2], length=vec.length))
				} else {
					return(paste0(seq(from=tmp[1], length=vec.length), "-", seq(from=tmp[2], length=vec.length)))
				}
			} else {
				return(seq(from=as.numeric(strsplit(as.character(year), "[.]")[[1]][1])+add.sub, length=vec.length))
			}
		} else {
			if (length(grep("_", year) > 0)) {
		 		tmp <- as.numeric(unlist(strsplit(strsplit(as.character(year), "[.]")[[1]][1], "_")))+add.sub
				if (output.type=="numeric") {
					return(seq(from=tmp[2], length=vec.length))
				} else {
					return(paste(season, seq(from=tmp[1], length=vec.length)))
				}
			} else {
				return(paste(season, seq(from=as.numeric(strsplit(as.character(year), "[.]")[[1]][1])+add.sub, length=vec.length)))
			}
		}
	}

	stextGrob <- function (label, r=0.1, x = x, y = y,
		just = "centre", hjust = NULL, vjust = NULL, rot = 0, check.overlap = FALSE,
		default.units = "native", name = NULL, gp = gpar(), vp = NULL){
		# http://stackoverflow.com/questions/7734535/control-font-thickness-without-changing-font-size

		let <- textGrob("a", gp=gp, vp=vp)
		wlet <- grobWidth(let)
		hlet <- grobHeight(let)

		tg <- textGrob(label=label, x=x, y=y, gp=gpar(col="white"), just = just, hjust = hjust, vjust = vjust, rot = rot,
				check.overlap = check.overlap, default.units = default.units)

		tgl <- c(lapply(seq(0, 2*pi, length=36), function(theta){
		  textGrob(label=label,x=x+cos(theta)*r*wlet, y=y+sin(theta)*r*hlet, gp=gpar(col="black"),
				just = just, hjust = hjust, vjust = vjust, rot = rot, check.overlap = check.overlap, default.units = default.units)
		  }), list(tg))

		g <- gTree(children=do.call(gList, tgl), vp=vp, name=name, gp=gp)
	}

	grid.stext <- function(...){
		g <- stextGrob(...)
		grid.draw(g)
		invisible(g)
	}

	labelSplit <- function(label, force.split.nchar=9) {
			label.split <- unlist(strsplit(label, " "))
			label.word.nchar <- cumsum(nchar(label.split))
			if (length(label.word.nchar) > 1 & tail(label.word.nchar, 1) > force.split.nchar) {
				if (any(label.word.nchar <= force.split.nchar)) {
					label.split.position <- max(which(label.word.nchar <= force.split.nchar))
					tmp.label <- paste(label.split[1:label.split.position], collapse=" ")
					tmp.label[2] <- paste(label.split[(label.split.position+1):length(label.split)], collapse=" ")
					tmp.cex <- c(title.ca.size - 0.25 - max(0, nchar(tmp.label[1])-force.split.nchar)*0.1, title.ca.size - 0.25 - max(0, nchar(tmp.label[2])-force.split.nchar)*0.1)
				} else {
					tmp.label <- label.split
					tmp.cex <- rep(title.ca.size - 0.25 - (max(nchar(tmp.label))-force.split.nchar)*0.1, length(tmp.label))
				}
				return(list(content_area.label.pieces=tmp.label, cex=tmp.cex))
			} else {
				return(list(content_area.label.pieces=label, cex=title.ca.size - max(0, nchar(label)-force.split.nchar)*0.1))
			}
	}

	###
	### END Utility functions
	###

	grade.values <- interpolate.grades(Grades, Content_Areas, studentGrowthPlot.year.span, Scale_Scores)

	if (any(duplicated(grade.values$interp.df))) {
		grade.values$interp.df <- unique(grade.values$interp.df) #  needed when transition grade is the same - e.g. Grade 9 ELA to Grade 9 PSAT (not 9 to EOCT)
	}

	grade.values$interp.df <- subset(grade.values$interp.df, !(is.na(GRADE) & is.na(GRADE_NUMERIC) & is.na(CONTENT_AREA)))
	grade.values$years <- tail(grade.values$years, nrow(grade.values$interp.df))

	if (!is.null(Report_Parameters[['Assessment_Transition']]) && grade.values$years[2] >= Report_Parameters[['Assessment_Transition']][['Year']]) Report_Parameters[['Assessment_Transition']] <- NULL

	if (!is.null(transition.grade)) {
		grade.rpt.index <- intersect(grep(paste0("^", Grades[!is.na(Grades)][1], "$"), grades.content_areas.reported.in.state$GRADE), grep(paste0("^", Content_Areas[1], "$"), grades.content_areas.reported.in.state$CONTENT_AREA)) #  needed when transition grade is the same - e.g. Grade 9 ELA to Grade 9 PSAT (not 9 to EOCT)
		if (length(grade.rpt.index)==0) grade.rpt.index <- (max(grep(max(Grades, na.rm = TRUE), grades.content_areas.reported.in.state$GRADE)) + min(which(!is.na(Grades))) - 1)
		if (any(grades.content_areas.reported.in.state$CONTENT_AREA[grade.rpt.index:nrow(grades.content_areas.reported.in.state)] %in% post.trans.content_areas)) {
			if (!is.list(Report_Parameters[['Assessment_Transition']])) {
				Report_Parameters[['Assessment_Transition']] <- list(Grade = transition.grade)
			} else Report_Parameters[['Assessment_Transition']][['Grade']] <- transition.grade
			Report_Parameters[['Assessment_Transition']][['Assessment_Transition_Type']] <- c(
				SGP::SGPstateData[[Report_Parameters$State]][["Assessment_Program_Information"]][["Assessment_Transition"]][['Vertical_Scale']],
				SGP::SGPstateData[[Report_Parameters$State]][["Assessment_Program_Information"]][["Assessment_Transition"]][[paste('Vertical_Scale', transition.grade, sep=".")]])

			grd.index <- grep(paste(SGP::SGPstateData[[Report_Parameters$State]][["Assessment_Program_Information"]][["Assessment_Transition"]][[paste0('Grades_Tested.', transition.grade)]], collapse="|"), grade.values$interp.df$GRADE)
			sbj.index <- grep(paste(names(SGP::SGPstateData[[Report_Parameters$State]][["Assessment_Program_Information"]][["Assessment_Transition"]][[paste0('Content_Areas_Labels.', transition.grade)]]), collapse="|"), grade.values$interp.df$CONTENT_AREA)

			if (length(sbj.index) > 0 & length(grd.index) > 0) {
			Report_Parameters[['Assessment_Transition']][['Year']] <- as.data.table(grades.content_areas.reported.in.state)[
				GRADE == grade.values$interp.df[min(intersect(grd.index, sbj.index)),]$GRADE &
				CONTENT_AREA == grade.values$interp.df[min(intersect(grd.index, sbj.index)),]$CONTENT_AREA, YEAR]
			} else Report_Parameters[['Assessment_Transition']][['Year']] <- grade.values$years[length(Grades)]

			if (!is.na(grade.values$years[length(Grades)]) & grade.values$years[length(Grades)] < Report_Parameters[['Assessment_Transition']][['Year']]) Report_Parameters[['Assessment_Transition']][['Year']] <- grade.values$years[length(Grades)]
		}
		# if (!transition.grade %in% grades.content_areas.reported.in.state$GRADE[1:(grade.rpt.index+1)] & !any(grade.values$interp.df$GRADE_NUMERIC > transition.grade.numeric, na.rm = TRUE)) Report_Parameters[['Assessment_Transition']] <- NULL
	}

	if (is.null(Report_Parameters[['Assessment_Transition']])) {
		achievement.level.labels <- list(SGP::SGPstateData[[Report_Parameters$State]][["Student_Report_Information"]][["Achievement_Level_Labels"]])
		number.achievement.level.regions <- sapply(achievement.level.labels, length)
		level.to.get.cuku <- list(which.max(SGP::SGPstateData[[Report_Parameters$State]][["Achievement"]][["Levels"]][["Proficient"]]=="Proficient")-1)
		level.to.get.musu <- list(which.max(SGP::SGPstateData[[Report_Parameters$State]][["Achievement"]][["Levels"]][["Proficient"]]=="Proficient"))
	} else {
		achievement.level.labels <- SGP::SGPstateData[[Report_Parameters$State]][["Assessment_Program_Information"]][["Assessment_Transition"]][
			grep("Achievement_Level_Labels", names(SGP::SGPstateData[[Report_Parameters$State]][["Assessment_Program_Information"]][["Assessment_Transition"]]))]
		number.achievement.level.regions <- sapply(achievement.level.labels, length)
		achievement.levels.proficiency <- SGP::SGPstateData[[Report_Parameters$State]][["Assessment_Program_Information"]][["Assessment_Transition"]][
			grep("Achievement_Levels", names(SGP::SGPstateData[[Report_Parameters$State]][["Assessment_Program_Information"]][["Assessment_Transition"]]))]
		level.to.get.cuku <- sapply(achievement.levels.proficiency, function(x) which.max(x[['Proficient']]=="Proficient")-1)
		level.to.get.musu <- sapply(achievement.levels.proficiency, function(x) which.max(x[['Proficient']]=="Proficient"))
	}

	achievement.level.region.colors <- lapply(number.achievement.level.regions, function(x) paste0("grey", round(seq(62, 91, length=x))))

	if (!is.null(Report_Parameters[['Assessment_Transition']][['Assessment_Transition_Type']])) {
		if (identical(toupper(Report_Parameters[['Assessment_Transition']][['Assessment_Transition_Type']][1]), "NO")) {
			tmp.cutscore.year <- tail(head(sort(unique(Cutscores[['YEAR']]), na.last=FALSE), -1), 1)
			if (!is.null(transition.grade)) tmp.cutscore.year <- NA
			tmp.cutscore.grade <- grade.values[['interp.df']][['GRADE']][which(grade.values[['years']]==Report_Parameters[['Assessment_Transition']][['Year']])]
			if (!tmp.cutscore.grade %in% Cutscores[['GRADE']]) {
				tmp.grades.reported <- SGP::SGPstateData[[Report_Parameters$State]][["Student_Report_Information"]][["Grades_Reported"]][[Report_Parameters$Content_Area]]
				tmp.cutscore.grade <-
					tmp.grades.reported[which.max(as.numeric(grade.values[['interp.df']][['GRADE']][which(grade.values[['years']]==Report_Parameters[['Assessment_Transition']][['Year']])]) < tmp.grades.reported)-1]
			}
			if (is.na(tmp.cutscore.year)) {
				Cutscores[is.na(YEAR), CUTSCORES:=Cutscores[GRADE==tmp.cutscore.grade & is.na(YEAR)][['CUTSCORES']]]
			} else {
				Cutscores[is.na(YEAR) | YEAR < Report_Parameters$Assessment_Transition$Year,
					CUTSCORES:=Cutscores[GRADE==tmp.cutscore.grade & YEAR==tmp.cutscore.year][['CUTSCORES']]]
			}
		}
		if (identical(toupper(Report_Parameters[['Assessment_Transition']][['Assessment_Transition_Type']][2]), "NO")) {
			tmp.cutscore.grade <- Grades[which(Years==Report_Parameters[['Assessment_Transition']][['Year']])]
			if (length(tmp.cutscore.grade)==0) tmp.cutscore.grade <- NA
			if (is.na(tmp.cutscore.grade)) {
				tmp.cutscore.grade <- grade.values[['interp.df']][['GRADE']][which(grade.values[['years']]==Report_Parameters[['Assessment_Transition']][['Year']])]
			}
			Cutscores[!is.na(YEAR) & YEAR >= Report_Parameters$Assessment_Transition$Year,
				CUTSCORES:=Cutscores[GRADE==tmp.cutscore.grade & YEAR >= Report_Parameters$Assessment_Transition$Year][['CUTSCORES']]]
		}
	}

	if (grade.values[['any_scale_scores']]) {
		low.year <- year.function(Report_Parameters$Current_Year, (1-grade.values$year_span), 1)
		high.year <- year.function(Report_Parameters$Current_Year, studentGrowthPlot.year.span-grade.values$year_span, 1)
		year.text <- c(year.function(Report_Parameters$Current_Year, (1-grade.values$year_span), grade.values$year_span+grade.values$increment_for_projection_current, "character", test.season),
			rep(" ", studentGrowthPlot.year.span))
		year.text <- head(year.text, studentGrowthPlot.year.span)
		content_area.text <- grade.values$interp.df$CONTENT_AREA[match(gsub("-", "_", year.text), sapply(grade.values$years, function(f) strsplit(f, "[.]")[[1]][1]))]
		content_area.text[is.na(content_area.text)] <- " "
		for (i in which(content_area.text %in% names(SGP::SGPstateData[[Report_Parameters$State]][["Student_Report_Information"]][["Content_Areas_Labels"]]))) {
			content_area.text[i] <- SGP::SGPstateData[[Report_Parameters$State]][["Student_Report_Information"]][["Content_Areas_Labels"]][[content_area.text[i]]]
		}

		if (grade.values$increment_for_projection_current > 0) {
			grades.text.numbers <- c(Grades[grade.values$year_span:1],
				grades.content_areas.reported.in.state$GRADE[match(Grades[1], grades.content_areas.reported.in.state$GRADE)+seq(grade.values$increment_for_projection_current)])
			tmp.grades.text.numbers <- head(grade.values$interp.df$GRADE[-1], studentGrowthPlot.year.span)
		} else {
			grades.text.numbers <- Grades[grade.values$year_span:1]
			tmp.grades.text.numbers <- head(grade.values$interp.df$GRADE[-1], studentGrowthPlot.year.span)
		}
		if (!is.null(Report_Parameters[['Configuration']][['Zero_to_K']])) {
			if (is.character(Report_Parameters[['Configuration']][['Zero_to_K']])) {
				grades.text.numbers[which(grades.text.numbers==0)] <- Report_Parameters[['Configuration']][['Zero_to_K']]
			} else grades.text.numbers[which(grades.text.numbers==0)] <- "K"
		}
		grades.text.numbers.missing <- which(is.na(grades.text.numbers))
		grades.text.numbers.non.tested <- which(!tmp.grades.text.numbers %in% grades.content_areas.reported.in.state$GRADE)
		grades.text.eoct <- which(grades.text.numbers=="EOCT")
		grades.text <- c(paste(grade.label, grades.text.numbers), rep(" ", studentGrowthPlot.year.span))
		grades.text[grades.text.numbers.missing] <- missing.data.symbol
		grades.text[grades.text.numbers.non.tested] <- "Non-tested Grade"
		if (print.eoct.content.area) {
			grades.text[grades.text.eoct] <- content_area.text[grades.text.eoct]
		} else grades.text[grades.text.eoct] <- "EOCT"
		grades.text <- head(grades.text, studentGrowthPlot.year.span)

		if (missing(Plotting_Scale_Scores)) Plotting_Scale_Scores <- Scale_Scores
		scale.scores.values <- c(Plotting_Scale_Scores[grade.values$year_span:1], rep(NA, studentGrowthPlot.year.span))
		scale.scores.values <- head(scale.scores.values, studentGrowthPlot.year.span)

		scale.scores.text <- c(Scale_Scores[grade.values$year_span:1], rep(" ", studentGrowthPlot.year.span))
		scale.scores.text[which(is.na(Scale_Scores[grade.values$year_span:1]))] <- missing.data.symbol
		scale.scores.text <- head(scale.scores.text, studentGrowthPlot.year.span)

		ach.levels.text <- c(ach.level.labels(Achievement_Levels[grade.values$year_span:1]), rep(" ", studentGrowthPlot.year.span))
		ach.levels.text <- head(ach.levels.text, studentGrowthPlot.year.span)

		if (grade.values[['year_span']] > 1) {
			gp.values <- c(SGP[(grade.values$year_span-1):1], rep(NA, studentGrowthPlot.year.span))
			gp.values <- head(gp.values, studentGrowthPlot.year.span-1)

			gp.text <- c(SGP[(grade.values$year_span-1):1], rep(" ", studentGrowthPlot.year.span))
			gp.text[which(is.na(SGP[(grade.values$year_span-1):1]))] <- missing.data.symbol
			gp.text <- head(gp.text, studentGrowthPlot.year.span-1)

			gp.levels.text <- c(sgp.level.labels(SGP_Levels[(grade.values$year_span-1):1]), rep(" ", studentGrowthPlot.year.span))
			gp.levels.text <- head(gp.levels.text, studentGrowthPlot.year.span-1)
		} else {
			gp.values <- rep(NA, studentGrowthPlot.year.span-1)
			gp.text <- rep(" ", studentGrowthPlot.year.span-1)

			gp.levels.text <- rep(" ", studentGrowthPlot.year.span-1)
		}

		if (grade.values[['increment_for_projection_current']]==0) {
			cuts.ny1 <- rep(NA, number.growth.levels+1)
			cuts.ny1.text <- rep(NA, number.growth.levels+1)
		} else {
			cuts.ny1 <- Plotting_Cuts[["NY1"]]
			cuts.ny1.text <- Cuts[["NY1"]]
		}
	} else {
		low.year <- year.function(Report_Parameters$Current_Year, (1-grade.values$year_span), 1)
		high.year <- year.function(Report_Parameters$Current_Year, studentGrowthPlot.year.span-grade.values$year_span, 1)
		year.text <- rep(" ", studentGrowthPlot.year.span)
		content_area.text <- rep(" ", studentGrowthPlot.year.span)
		grades.text <- rep(" ", studentGrowthPlot.year.span)
		scale.scores.values <- rep(NA, studentGrowthPlot.year.span)
		scale.scores.text <- rep(" ", studentGrowthPlot.year.span)
		ach.levels.text <- rep(" ", studentGrowthPlot.year.span)
		gp.values <- rep(NA, studentGrowthPlot.year.span-1)
		gp.text <- rep(" ", studentGrowthPlot.year.span-1)
		gp.levels.text <- rep(" ", studentGrowthPlot.year.span-1)
		cuts.ny1 <- rep(NA, number.growth.levels+1)
		cuts.ny1.text <- rep(NA, number.growth.levels+1)
	}

	current.year <- year.function(Report_Parameters$Current_Year, 0, 1)
	xscale.range <- range(low.year,high.year) + c(-0.075, 0.1)*diff(range(low.year,high.year))
	if (is.null(Report_Parameters$Assessment_Transition)) {
		tmp.year.cut <- NULL
		xscale.range.list <- list(xscale.range)
	} else {
		tmp.year.cut <- year.function(Report_Parameters$Assessment_Transition$Year, 0, 1)-0.5
		if (tmp.year.cut <= xscale.range[1]) xscale.range[1] <- tmp.year.cut-0.9
		xscale.range.list <- list(c(xscale.range[1], tmp.year.cut-0.025), c(tmp.year.cut+0.025, xscale.range[2]))
	}

	low.score <- min(cuts.ny1, Plotting_Scale_Scores, Cutscores[GRADE==data.table(grade.values[['interp.df']])[!is.na(CONTENT_AREA)][['GRADE']][1] & CUTLEVEL==1][['CUTSCORES']], na.rm=TRUE)
	high.score <- max(cuts.ny1, Plotting_Scale_Scores, Cutscores[GRADE==tail(data.table(grade.values[['interp.df']])[!is.na(CONTENT_AREA)][['GRADE']], 1) & CUTLEVEL %in% (number.achievement.level.regions-1)][['CUTSCORES']], na.rm=TRUE)
	yscale.range <- extendrange(c(low.score,high.score), f=0.15)

	content_area.label.pieces <- labelSplit(content.area.label)
	if (length(content_area.label.pieces[[1]])==1) split.content_area.tf <- FALSE else split.content_area.tf <- TRUE


	### Create viewports

	if (is.null(Report_Parameters$Assessment_Transition)) {
		subject.report.vp <- viewport(layout = grid.layout(2, 3, widths = unit(c(1.15, 5.4, 1.5)/8.05, rep("npc", 3)),
						heights = unit(c(2.45, 0.9)/3.35, rep("npc", 2))), gp=gpar(fill="transparent"))
	} else {
		subject.report.vp <- viewport(layout = grid.layout(2, 3, widths = unit(c(1.15, 5.65, 1.25)/8.05, rep("npc", 3)),
						heights = unit(c(2.45, 0.9)/3.35, rep("npc", 2))), gp=gpar(fill="transparent"))
	}

	growth.chart.vp <- viewport(name="growth.chart.vp",
		layout.pos.row=1, layout.pos.col=2,
		xscale=xscale.range,
		yscale=yscale.range,
		gp=gpar(fill="transparent"))

	if (is.null(Report_Parameters$Assessment_Transition)) {
		right.vp <- viewport(name="right.vp",
			layout.pos.row=1, layout.pos.col=3,
			xscale=c(0,1),
			yscale=c(0,1),
			gp=gpar(fill="transparent"))
	} else {
		right.vp <- viewport(name="right.vp",
			layout.pos.row=1, layout.pos.col=3,
			xscale=c(0,1),
			yscale=yscale.range,
			gp=gpar(fill="transparent"))
	}

	left.vp <- viewport(name="left.vp",
		layout.pos.row=1, layout.pos.col=1,
		xscale=c(0, 1),
		yscale=yscale.range,
		gp=gpar(fill="transparent"))

	if (is.null(Report_Parameters$Assessment_Transition)) {
		growth.and.margins.vp <- viewport(name="growth.and.margins.vp",
			layout.pos.row=1, layout.pos.col=1:2)
	} else {
		growth.and.margins.vp <- viewport(name="growth.and.margins.vp",
			layout.pos.row=1, layout.pos.col=1:3)
		bottom.right.vp.size <- 1.05
	}

	bottom.vp <- viewport(name="bottom.vp",
		layout.pos.row=2, layout.pos.col=2,
		xscale=xscale.range,
		yscale=c(0,3),
		gp=gpar(fill="transparent"))

	bottom.left.right.vp <- viewport(name="bottom.left.right.vp",
		layout.pos.row=2, layout.pos.col=1:3,
		xscale=c(0,1),
		yscale=c(0,3),
		gp=gpar(fill="transparent"))

	bottom.right.vp <- viewport(name="bottom.right.vp",
		layout.pos.row=2, layout.pos.col=3,
		xscale=c(0,1),
		yscale=c(0,3),
		gp=gpar(fill="transparent"))

	bottom.left.vp <- viewport(name="bottom.left.vp",
		layout.pos.row=2, layout.pos.col=1,
		xscale=c(0,1),
		yscale=c(0,3),
		gp=gpar(fill="transparent"))

	no.data.vp <- viewport(name="no.data.vp",
		layout.pos.row=1, layout.pos.col=1:3,
		xscale=c(0,1),
		yscale=c(0,1),
		gp=gpar(fill="transparent"))

	pushViewport(subject.report.vp)


	#############################################
	### Growth.Chart Viewport
	#############################################

	pushViewport(growth.chart.vp)

	if (is.null(tmp.year.cut)) {
		tmp.year.sequence <- list(as.character((low.year-1):(high.year+1)))
	} else {
		tmp.year.sequence <- list(as.character((low.year-1):tmp.year.cut), as.character(rev((high.year+1):tmp.year.cut)))
	}

	for (j in seq(length(Report_Parameters$Assessment_Transition[['Year']])+1)) {
		for (i in seq(number.achievement.level.regions[[j]]-1)) {
			temp <- data.table(temp_id=seq_len(nrow(grade.values$interp.df)), grade.values$interp.df, YEAR=sapply(strsplit(sapply(strsplit(grade.values$years, "_"), tail, 1), "[.]"), head, 1), key="YEAR")[tmp.year.sequence[[j]]]
			temp[,YEAR:=get.my.cutscore.year(Report_Parameters$State, Report_Parameters$Content_Area, YEAR, i)]
			if (class(temp$YEAR) != class(Cutscores$YEAR)) class(temp$YEAR) <- class(Cutscores$YEAR)
			temp <- merge(temp, Cutscores[CUTLEVEL==i], all.x=TRUE, by=c("YEAR", "CONTENT_AREA", "GRADE"))
			temp <- temp[order(temp$temp_id),][['CUTSCORES']]
			if (length(temp[which(!is.na(temp))])==1) {
				temp[which(is.na(temp))] <- temp[which(!is.na(temp))]
			} else {
				temp[which(is.na(temp))] <- approx(temp, xout=which(is.na(temp)), rule=2)$y
			}
			assign(paste0("level_", j, "_", i, "_curve"), splinefun(as.numeric(tmp.year.sequence[[j]]), temp, method="monoH.FC"))
		}

		tmp.x.points <- seq(xscale.range.list[[j]][1], xscale.range.list[[j]][2], length=round(diff(xscale.range.list[[j]])/diff(xscale.range)*40))
		x.boundary.values.1 <- c(xscale.range.list[[j]][1], tmp.x.points, xscale.range.list[[j]][2])
		y.boundary.values.1 <- c(yscale.range[1], eval(parse(text=paste0("level_", j, "_1_curve(tmp.x.points)"))), yscale.range[1])
		assign(paste0("x.boundary.values.", number.achievement.level.regions[[j]]),
			c(xscale.range.list[[j]][1], tmp.x.points, xscale.range.list[[j]][2]))
		assign(paste0("y.boundary.values.", number.achievement.level.regions[[j]]),
			c(yscale.range[2], eval(parse(text=paste0("level_", j, "_", number.achievement.level.regions[[j]]-1, "_curve(tmp.x.points)"))), yscale.range[2]))

		if (number.achievement.level.regions[[j]] > 2) {
			for (i in 2:(number.achievement.level.regions[[j]]-1)) {
				assign(paste0("x.boundary.values.", i),
					c(tmp.x.points, rev(tmp.x.points)))

				assign(paste0("y.boundary.values.", i),
					eval(parse(text=paste0("c(level_", j, "_", i, "_curve(tmp.x.points), level_", j, "_", i-1, "_curve(rev(tmp.x.points)))"))))
			}
		}

		## Keep achievement level bands from extending outside of chart box
		eval(parse(text=paste0("y.boundary.values.", seq(number.achievement.level.regions[[j]]), "[y.boundary.values.", seq(number.achievement.level.regions[[j]]), " > max(yscale.range)] <- max(yscale.range)")))
		eval(parse(text=paste0("y.boundary.values.", seq(number.achievement.level.regions[[j]]), "[y.boundary.values.", seq(number.achievement.level.regions[[j]]), " < min(yscale.range)] <- min(yscale.range)")))

		for (i in seq(number.achievement.level.regions[[j]])) {
			grid.polygon(x=get(paste0("x.boundary.values.", i)),
				y=get(paste0("y.boundary.values.", i)),
				default.units="native",
				gp=gpar(fill=achievement.level.region.colors[[j]][i], lwd=0.8, col="white"))
		}
	}

	if (!is.null(Report_Parameters$Assessment_Transition)) {
		grid.lines(x=tmp.year.cut, y=yscale.range, default.units="native", gp=gpar(lwd=1.8, col=border.color))
		grid.lines(x=tmp.year.cut+0.028, y=yscale.range, default.units="native", gp=gpar(lwd=0.4, col=border.color))
		grid.lines(x=tmp.year.cut-0.028, y=yscale.range, default.units="native", gp=gpar(lwd=0.4, col=border.color))

		if (grade.values[['any_scale_scores']]) {
			tmp.transition.names <- names(SGP::SGPstateData[[Report_Parameters$State]][["Assessment_Program_Information"]][["Assessment_Transition"]])
			tmp.test.abbreviation <-
				unlist(SGP::SGPstateData[[Report_Parameters$State]][["Assessment_Program_Information"]][["Assessment_Transition"]][grep('Assessment_Abbreviation', tmp.transition.names)])
			tmp.test.abbreviation.text <- rep(" ", length(year.text))
			tmp.test.abbreviation.text[which((low.year:high.year < Report_Parameters$Assessment_Transition$Year)[which(year.text!=" ")])] <-
				tmp.test.abbreviation[1]
			tmp.test.abbreviation.text[which((low.year:high.year >= Report_Parameters$Assessment_Transition$Year)[which(year.text!=" ")])] <-
				tmp.test.abbreviation[2]
			tmp.test.abbreviation.text[which(content_area.text==" ")] <- " "
			grid.stext(tmp.test.abbreviation.text, x=unit(low.year:high.year, "native"), y=convertHeight(unit(0.07, "npc"), "native"), gp=gpar(cex=0.7))
			grid.stext(sapply(content_area.text, capwords), x=unit(low.year:high.year, "native"), y=convertHeight(unit(0.03, "npc"), "native"), gp=gpar(cex=0.7))
		}
	}

	if (is.null(Report_Parameters$Assessment_Transition) && sgPlot.show.content_area.progression) {
		grid.stext(sapply(content_area.text, capwords), x=unit(low.year:high.year, "native"), y=convertHeight(unit(0.05, "npc"), "native"), gp=gpar(cex=0.75))
		# grid.text(x=low.year:high.year, y=convertHeight(unit(0.05, "npc"), "native"), sapply(content_area.text, capwords), gp=gpar(col="white", cex=0.75), default.units="native")
	}

	if (connect.points=="Arrows") {
		growth.arrow.coors.x <- c(.05, .85, .8, 1, .8, .85, .05, .053, .0555, .0575, .0585, .059, .0585, .0575, .0555, .053, .05)
		growth.arrow.coors.y <- c(-.2, -.2, -.5, 0, .5, .2,  seq(.2, -.2, length=11))

		for (i in 1:length(gp.values)) {
			tmp.lag <- which(is.na(rev(scale.scores.values[1:i]))==FALSE)
			if (!is.na(gp.values[i]) & length(tmp.lag) > 0) {
				lag.to.prior.score <- min(tmp.lag, na.rm=TRUE)
				if (lag.to.prior.score == 1) my.lty <- 1 else my.lty <- 2
				arrow.rise <- convertHeight(unit(scale.scores.values[i+1], "native") - unit(scale.scores.values[i+1-lag.to.prior.score], "native"), "inches")
				arrow.run <- convertWidth(unit(lag.to.prior.score, "native") - unit(0, "native"), "inches")
				arrow.angle <- atan2(as.numeric(arrow.rise),as.numeric(arrow.run))*180/pi

				## Arrows connecting achievement scores
				if (!is.na(arrow.angle)) {
				pushViewport(viewport(x=unit(low.year+i-lag.to.prior.score, "native"), y=unit(scale.scores.values[i+1-lag.to.prior.score], "native"),
					width=unit((lag.to.prior.score-0.08)/cos(arrow.angle*pi/180), "native"), height=unit(0.05, "npc"), angle=arrow.angle, just=c("left", "center"),
					xscale=c(0, 1), yscale=c(-0.5, 0.5)))
				grid.polygon(x=growth.arrow.coors.x, y=growth.arrow.coors.y, default.units="native",
					gp=gpar(lwd=0.3, lty=my.lty, col=border.color, fill=arrow.color(as.numeric(gp.values[i]))))
				grid.curve(0.05, -0.2, 0.05, 0.2, curvature=0.3, ncp=11, square=FALSE, default.units="native", gp=gpar(lwd=0.3, col=border.color))
				popViewport()
				}
			}
		}
	} ## END Report_Parameters[['Configuration']][['Connect_Points']]=="Arrows"


	if (paste(Grades[1], Content_Areas[1]) != tail(with(grades.content_areas.reported.in.state, paste(GRADE, CONTENT_AREA)), 1) & !is.na(cuts.ny1[1]) & show.fan){
		for (i in seq(number.growth.levels)) {
			grid.polygon(x=c(current.year, rep(current.year+grade.values$increment_for_projection_current, 2), current.year),
				y=c(scale.scores.values[which(current.year==low.year:high.year)], max(yscale.range[1], cuts.ny1[i]),
					min(yscale.range[2], cuts.ny1[i+1]), scale.scores.values[which(current.year==low.year:high.year)]),
				default.units="native", gp=gpar(col=NA, lwd=0, fill=arrow.legend.color[i], alpha=0.45))
			if (fan.extra) {
				fan.extra.col <- arrow.legend.color[i]
				fan.extra.x <- c(current.year, rep(current.year+grade.values$increment_for_projection_current, 2), current.year)
				fan.extra.y <- c(scale.scores.values[which(current.year==low.year:high.year)], max(yscale.range[1], cuts.ny1[i]),
					             min(yscale.range[2], cuts.ny1[i+1]), scale.scores.values[which(current.year==low.year:high.year)])
				eval(parse(text = fan.extras[i]))
			}
			grid.roundrect(x=unit(current.year+grade.values$increment_for_projection_current, "native"),
				y=unit((max(yscale.range[1], cuts.ny1[i])+min(yscale.range[2], cuts.ny1[i+1]))/2, "native"),
				height=unit(min(yscale.range[2], as.numeric(cuts.ny1[i+1])) - max(yscale.range[1], as.numeric(cuts.ny1[i])), "native"),
				width=unit(0.04, "native"), r=unit(0.45, "snpc"), gp=gpar(lwd=0.3, col=border.color, fill=arrow.legend.color[i]))

			if (show.fan.growth.labels && is.null(Report_Parameters[['SGP_Targets']])) {
				grid.text(x=current.year+grade.values$increment_for_projection_current+0.05,
					y=(max(yscale.range[1], cuts.ny1[i])+min(yscale.range[2], cuts.ny1[i+1]))/2, growth.level.labels[i],
					default.units="native", just="left", gp=gpar(cex=0.4, col=border.color))
			}
			if (i != 1 & show.fan.cutscores) {
				grid.text(x=current.year+grade.values$increment_for_projection_current+0.05, y=cuts.ny1[i], as.character(cuts.ny1.text[i]),
					default.units="native", just="left", gp=gpar(cex=0.4, col=border.color))
			}
		}
	}

	if (!is.null(Report_Parameters[['SGP_Targets']])) {
		for (i in tmp.target.types) {
			if (length(grep("Current", i))==0) {
				current.year.x.coor <- current.year
				current.year.x.coor.lag <- min(which(!is.na(tail(Scale_Scores, -1))), na.rm=TRUE)
				x.coor.label.adjustment <- -0.075; label.position <- "right"; tmp.index <- 1
				if (is.null(Report_Parameters$Assessment_Transition)) {
					tmp.achievement.level <- which(tail(head(Achievement_Levels, current.year.x.coor.lag+1), 1)==achievement.level.labels[[tmp.index]])
				} else {
					if (year.function(Report_Parameters$Assessment_Transition$Year, 0, 1) <= current.year.x.coor-current.year.x.coor.lag) tmp.index <- 2
					tmp.achievement.level <- which(tail(head(Achievement_Levels, current.year.x.coor.lag+1), 1)==achievement.level.labels[[tmp.index]])
				}
				show.targets <- TRUE
			} else {
				current.year.x.coor <- current.year+grade.values$increment_for_projection_current
				current.year.x.coor.lag <- grade.values$increment_for_projection_current
				x.coor.label.adjustment <- 0.075; label.position <- "left"; tmp.index <- 1; show.targets <- show.fan
				if (is.null(Report_Parameters$Assessment_Transition)) {
					tmp.achievement.level <- which(head(Achievement_Levels, 1)==achievement.level.labels[[tmp.index]])
				} else {
					if (year.function(Report_Parameters$Assessment_Transition$Year, 0, 1) <= current.year.x.coor) tmp.index <- 2
					tmp.achievement.level <- which(head(Achievement_Levels, 1)==achievement.level.labels[[tmp.index]])
				}
			}

			if (show.targets) {
				if (length(grep("CUKU", i))>0 & tmp.achievement.level <= level.to.get.cuku[[tmp.index]]) {
					label.position <- c(label.position, "center")
					tmp.target.label <- c(CU.label, target.label)
					y.coordinates <- c(as.numeric(convertHeight(convertHeight(unit(Plotting_SGP_Scale_Score_Targets[[i]][['NY1']], "native"), "inches")+unit(0.0375, "inches"), "native")),
							as.numeric(convertHeight(convertHeight(unit(Plotting_SGP_Scale_Score_Targets[[i]][['NY1']], "native"), "inches")-unit(0.0375, "inches"), "native")))
				}
				if (length(grep("CUKU", i))>0 & tmp.achievement.level > level.to.get.cuku[[tmp.index]]) {
					label.position <- c(label.position, "top")
					tmp.target.label <- c(KU.label, target.label)
					y.coordinates <- c(Plotting_SGP_Scale_Score_Targets[[i]][['NY1']],
						as.numeric(convertHeight(convertHeight(unit(Plotting_SGP_Scale_Score_Targets[[i]][['NY1']], "native"), "inches")-unit(0.1, "inches"), "native")))
				}
				if (length(grep("MUSU", i))>0 & tmp.achievement.level <= level.to.get.musu[[tmp.index]]) {
					label.position <- c(label.position, "bottom")
					tmp.target.label <- c(MU.label, target.label)
					y.coordinates <- c(as.numeric(convertHeight(convertHeight(unit(Plotting_SGP_Scale_Score_Targets[[i]][['NY1']], "native"), "inches")+unit(0.1, "inches"), "native")),
						Plotting_SGP_Scale_Score_Targets[[i]][['NY1']])
				}
				if (length(grep("MUSU", i))>0 & tmp.achievement.level > level.to.get.musu[[tmp.index]]) {
					label.position <- c(label.position, "bottom")
					tmp.target.label <- c(SU.label, target.label)
					y.coordinates <- c(as.numeric(convertHeight(convertHeight(unit(Plotting_SGP_Scale_Score_Targets[[i]][['NY1']], "native"), "inches")+unit(0.1, "inches"), "native")),
						Plotting_SGP_Scale_Score_Targets[[i]][['NY1']])
				}
				if (length(grep("CUSTOM", i))>0) {
					label.position <- c(label.position, "bottom")
					tmp.target.label <- NULL
					y.coordinates <- c(as.numeric(convertHeight(convertHeight(unit(Plotting_SGP_Scale_Score_Targets[[i]][['NY1']], "native"), "inches")+unit(0.1, "inches"), "native")),
						Plotting_SGP_Scale_Score_Targets[[i]][['NY1']])
				}
				grid.lines(x=c(current.year.x.coor-current.year.x.coor.lag, current.year.x.coor),
					y=c(scale.scores.values[which(current.year.x.coor-current.year.x.coor.lag==low.year:high.year)], Plotting_SGP_Scale_Score_Targets[[i]][['NY1']]),
					gp=gpar(lwd=0.8, col=border.color), default.units="native")
				grid.circle(x=current.year.x.coor, y=Plotting_SGP_Scale_Score_Targets[[i]][['NY1']], r=unit(c(0.05, 0.04, 0.025, 0.0125), "inches"),
					gp=gpar(col=c("black", "white", "black", "white"), lwd=0.01, fill=c("black", "white", "black", "white")), default.units="native")
				if (!is.null(tmp.target.label)) grid.text(x=current.year.x.coor+x.coor.label.adjustment, y=y.coordinates, tmp.target.label, default.units="native", just=label.position, gp=gpar(cex=0.5, col=border.color))
			}
		}
	}

	grid.circle(x=low.year:high.year, y=scale.scores.values, r=unit(0.04, "inches"), gp=gpar(col=border.color, lwd=0.7, fill="white"), default.units="native")

	# popViewport() Don't pop until after next section - extra mid-level distinction may be needed (first code chunk in 'Left Viewport')


	#################################
	### Left Viewport
	#################################

    unq.labels <- names(achievement.level.labels[[1]])
    if (length(unique(unq.labels)) != number.achievement.level.regions[[1]]) {
        # WIDA_GA for example - extra "mid-level" used for state exit criteria cut
        tmp.n.achlev.regions <- length(unique(unq.labels))
        dup.levels <- which(duplicated(unq.labels))
        unq.levels <- which(!duplicated(unq.labels))
        unq.levels <- unq.levels + c(diff(unq.levels) - 1, 0) # use (last) duplicate level for boundaries
        max.level <- max(unq.levels)
        mid.levels <- unq.levels %w/o% c(1, max.level)

        for (k in dup.levels) {
            tmp_fun <- get(paste0("level_1_", k - 1, "_curve"))
            y.dup.points <- tmp_fun(tmp.x.points)

            grid.lines(x = tmp.x.points, y = y.dup.points, default.units = "native",
                       gp = gpar(lwd = 1.4, lty = "dashed", col = "black"))
        }
    } else {
        tmp.n.achlev.regions <- max.level <- number.achievement.level.regions[[1]]
        unq.levels <- seq(tmp.n.achlev.regions)
        mid.levels <- 2:(tmp.n.achlev.regions - 1)
    }

    popViewport() # growth.chart.vp

    pushViewport(left.vp)

    y.boundary.legend.1 <- c(yscale.range[1], yscale.range[1], rep(level_1_1_curve(xscale.range[1]), 2))
    assign(paste0("y.boundary.legend.", max.level),
        c(yscale.range[2], yscale.range[2], rep(eval(parse(text = paste0("level_1", "_", max.level-1, "_curve(xscale.range[1])"))), 2)))

    if (tmp.n.achlev.regions > 2) {
        for (i in mid.levels) {
            ii <- unq.levels[which(unq.levels == i) - 1]
            assign(paste0("y.boundary.legend.", i), eval(parse(text = 
                   paste0("c(rep(level_1", "_", ii, "_curve(xscale.range[1]), 2), rep(level_1", 
                          "_", i, "_curve(xscale.range[1]), 2))"))))
        }
    }

    for (i in unq.levels){
        grid.polygon(x = c(0,1,1,0), y = get(paste0("y.boundary.legend.", i)),
                     default.units = "native",
                     gp = gpar(fill = achievement.level.region.colors[[1]][i], lwd = 0.5, col = border.color, alpha = 0.7))
    }

    grid.text(x = 0.94, y = (level_1_1_curve(xscale.range[1]) + yscale.range[1])/2, 
              label = unq.labels[1], default.units = "native", just = "right",
              gp = gpar(col = border.color, fontface = 2, fontfamily = font.fam, cex = left.vp.size, lineheight = legend.lheight))
    grid.text(x = 0.94, y = (eval(parse(text = paste0("level_1", "_", max.level-1, "_curve(xscale.range[1])"))) + yscale.range[2])/2,
              label = unq.labels[max.level], default.units = "native", just = "right",
              gp = gpar(col = border.color, fontface = 2, fontfamily = font.fam, cex = left.vp.size, lineheight = legend.lheight))

    if (tmp.n.achlev.regions > 2) {
        for (i in mid.levels) {
            ii <- unq.levels[which(unq.levels == i) - 1]
            grid.text(x = .94,
                      y = (eval(parse(text = paste0("(level_1", "_", ii, "_curve(xscale.range[1]) + level_1",
                                                    "_", i, "_curve(xscale.range[1]))/2")))),
                      label = unq.labels[i],
                      gp = gpar(col = border.color, fontface = 2, fontfamily = font.fam, cex = left.vp.size, lineheight = legend.lheight),
                      default.units = "native", just = "right")
        }
    }

	grid.lines(0, c(yscale.range[1], yscale.range[2]), gp = gpar(lwd = 0.8, col = border.color), default.units = "native")

	popViewport()


	############################################
	### Growth and Margins Viewport
	############################################

	pushViewport(growth.and.margins.vp)
		if (grade.values$year_span == 0) {
			grid.roundrect(r=unit(.01, "snpc"), gp=gpar(lwd=1.8, col=border.color, clip=TRUE, fill=rgb(1, 1, 1, 0.5)))
		} else {
			grid.roundrect(r=unit(.01, "snpc"), gp=gpar(lwd=1.8, col=border.color, clip=TRUE))
		}
	popViewport()


	##############################################
	### Bottom Viewport
	##############################################

	pushViewport(bottom.vp)
	if (is.null(Report_Parameters[['SGP_Targets']])) {
		grid.text(x=low.year:high.year, y=2.67, grades.text, gp=gpar(col=border.color, cex=.75), default.units="native")
		grid.text(x=low.year:high.year, y=2.3, year.text, gp=gpar(col=border.color, cex=.6), default.units="native")
		grid.text(x=low.year:high.year, y=1.7, scale.scores.text, gp=gpar(col=border.color, cex=.65), default.units="native")
		grid.text(x=low.year:high.year, y=1.3, ach.levels.text, gp=gpar(col=border.color, cex=.6), default.units="native")
		grid.text(x=(low.year+1):high.year-0.5, y=0.7, gp.text, gp=gpar(col=border.color, cex=.65), default.units="native")
		grid.text(x=(low.year+1):high.year-0.5, y=0.3, gp.levels.text, gp=gpar(col=border.color, cex=.6), default.units="native")
	} else {
		grid.text(x=low.year:high.year, y=2.75, grades.text, gp=gpar(col=border.color, cex=.6), default.units="native")
		grid.text(x=low.year:high.year, y=2.45, year.text, gp=gpar(col=border.color, cex=.5), default.units="native")
		grid.text(x=low.year:high.year, y=1.95, scale.scores.text, gp=gpar(col=border.color, cex=.5), default.units="native")
		grid.text(x=low.year:high.year, y=1.65, ach.levels.text, gp=gpar(col=border.color, cex=.5), default.units="native")
		grid.text(x=(low.year+1):high.year-0.5, y=0.85, gp.text, gp=gpar(col=border.color, cex=.5), default.units="native")
		grid.text(x=(low.year+1):high.year-0.5, y=0.55, gp.levels.text, gp=gpar(col=border.color, cex=.5), default.units="native")

		tmp.projection.names.list <- list()
		if (length(grep("Current", tmp.target.types)) > 0) {
			tmp.projection.names.list[["Current"]] <- grep("Current", tmp.target.types, value=TRUE)
		}

		if (length(grep("Current", tmp.target.types, invert=TRUE)) > 0) {
			tmp.projection.names.list[["Lagged"]] <- grep("Current", tmp.target.types, value=TRUE, invert=TRUE)
		}

		for (i in seq_along(tmp.projection.names.list)) {
			if (length(grep("Current", tmp.projection.names.list[[i]])) > 0) {
				tmp.projection.names <- tmp.projection.names.list[[i]]
				tmp.projection.year.from <- current.year
				tmp.projection.year.to <- current.year+grade.values$increment_for_projection_current
				if (!is.null(Report_Parameters$Assessment_Transition) && year.function(Report_Parameters$Assessment_Transition$Year, 0, 1) <= tmp.projection.year.from) {
					achievement.level.label.index.from <- 2
				} else {
					achievement.level.label.index.from <- 1
				}
				if (!is.null(Report_Parameters$Assessment_Transition) && year.function(Report_Parameters$Assessment_Transition$Year, 0, 1) <= tmp.projection.year.to) {
					achievement.level.label.index.to <- 2
				} else {
					achievement.level.label.index.to <- 1
				}
				tmp.achievement.level <- which(head(Achievement_Levels, 1)==achievement.level.labels[[achievement.level.label.index.from]])
			} else {
				tmp.projection.names <- tmp.projection.names.list[[i]]
				tmp.projection.year.lag <- min(which(!is.na(tail(Scale_Scores, -1))), na.rm=TRUE)
				tmp.projection.year.from <- current.year-tmp.projection.year.lag
				tmp.projection.year.to <- current.year
				if (!is.null(Report_Parameters$Assessment_Transition) && year.function(Report_Parameters$Assessment_Transition$Year, 0, 1) <= tmp.projection.year.from) {
					achievement.level.label.index.from <- 2
				} else {
					achievement.level.label.index.from <- 1
				}
				if (!is.null(Report_Parameters$Assessment_Transition) && year.function(Report_Parameters$Assessment_Transition$Year, 0, 1) <= tmp.projection.year.to) {
					achievement.level.label.index.to <- 2
				} else {
					achievement.level.label.index.to <- 1
				}
				tmp.achievement.level <- which(tail(head(Achievement_Levels, tmp.projection.year.lag+1), 1)==achievement.level.labels[[achievement.level.label.index.from]])
			}

			if (length(grep("CUSTOM", tmp.projection.names))==0) { ### Achievement/Growth Target Text associated with CUKU/MUSU targets
				if ((length(grep("CUKU", tmp.projection.names)) > 0 & tmp.achievement.level <= level.to.get.cuku[[achievement.level.label.index.from]]) |
					length(grep("MUSU", tmp.projection.names))==0) {
						level.to.get.cuku.label <- names(achievement.level.labels[[achievement.level.label.index.to]])[level.to.get.cuku[[achievement.level.label.index.to]]+1]
						grid.text(x=tmp.projection.year.to-0.5, y=1.35,
							paste0(level.to.get.cuku.label, " (", SGP_Scale_Score_Targets[[grep("CUKU", tmp.projection.names, value=TRUE)]][['NY1']], ")"),
							gp=gpar(col=border.color, cex=.4), default.units="native")
						grid.text(x=tmp.projection.year.to-0.5, y=0.25,
							paste0(CU.label, " (", SGP_Targets[[grep("CUKU", tmp.projection.names, value=TRUE)]], ")"),
							gp=gpar(col=border.color, cex=.4), default.units="native")
				} else {
					level.to.get.cuku.label <- names(achievement.level.labels[[achievement.level.label.index.to]])[level.to.get.cuku[[achievement.level.label.index.to]]+1]
					level.to.get.musu.label <- names(achievement.level.labels[[achievement.level.label.index.to]])[level.to.get.musu[[achievement.level.label.index.to]]+1]
					grid.text(x=tmp.projection.year.to-0.5, y=1.35,
						paste0(level.to.get.cuku.label, " (", SGP_Scale_Score_Targets[[grep("CUKU", tmp.projection.names, value=TRUE)]][['NY1']], ")/", level.to.get.musu.label, " (", SGP_Scale_Score_Targets[[grep("MUSU", tmp.projection.names, value=TRUE)]][['NY1']], ")"),
						gp=gpar(col=border.color, cex=.4), default.units="native")
					if (tmp.achievement.level <= level.to.get.musu[[achievement.level.label.index.from]]) {
						grid.text(x=tmp.projection.year.to-0.5, y=0.25,
							paste0(KU.label, " (", SGP_Targets[[grep('CUKU', tmp.projection.names, value=TRUE)]], ")/Move Up (", SGP_Targets[[grep('MUSU', tmp.projection.names, value=TRUE)]], ")"),
							gp=gpar(col=border.color, cex=.4), default.units="native")
					}
					if (tmp.achievement.level > level.to.get.musu[[achievement.level.label.index.from]]) {
						grid.text(x=tmp.projection.year.to-0.5, y=0.25,
							paste0(KU.label, " (", SGP_Targets[[grep('CUKU', tmp.projection.names, value=TRUE)]], ")/Stay Up (", SGP_Targets[[grep('MUSU', tmp.projection.names, value=TRUE)]], ")"),
							gp=gpar(col=border.color, cex=.4), default.units="native")
					}
				}
			} else { ### Achievement/Growth Target text associated with CUSTOM plotting
				level.to.get.cuku.label <- names(achievement.level.labels[[achievement.level.label.index.to]])[level.to.get.cuku[[achievement.level.label.index.to]]+1]
				grid.text(x=tmp.projection.year.to-0.5, y=1.35,
					paste0(level.to.get.cuku.label, " (", SGP_Scale_Score_Targets[[grep("CUSTOM", tmp.projection.names, value=TRUE)]][['NY1']], ")"),
					gp=gpar(col=border.color, cex=.4), default.units="native")
				grid.text(x=tmp.projection.year.to-0.5, y=0.25,
					paste0(CU.label, " (", SGP_Targets[[grep("CUSTOM", tmp.projection.names, value=TRUE)]], ")"),
					gp=gpar(col=border.color, cex=.4), default.units="native")
			}
		}
	}
	popViewport()


	##############################
	### Bottom Left.Right Viewport
	##############################

	pushViewport(bottom.left.right.vp)
	if (is.null(Report_Parameters[['SGP_Targets']])) {
		grid.lines(x=c(0,1), y=2, gp=gpar(lwd=1.8, col=border.color), default.units="native")
		grid.lines(x=c(0,1), y=1, gp=gpar(lwd=1, col=border.color), default.units="native")
		grid.lines(x=c(0,1), y=0, gp=gpar(lwd=1.8, col=border.color), default.units="native")
	} else {
		grid.lines(x=c(0,1), y=2.2, gp=gpar(lwd=1.8, col=border.color), default.units="native")
		grid.lines(x=c(0,1), y=1.1, gp=gpar(lwd=1, col=border.color), default.units="native")
		grid.lines(x=c(0,1), y=0, gp=gpar(lwd=1.8, col=border.color), default.units="native")
	}
	popViewport()


	################################
	### Bottom Right Viewport
	################################


	pushViewport(bottom.right.vp)
	if (is.null(Report_Parameters[['SGP_Targets']])) {
		grid.text(x=0.1, y=1.5, achievement.label, gp=gpar(col=border.color, cex=bottom.right.vp.size), just="left", default.units="native")
		grid.text(x=0.1, y=.5, growth.label, gp=gpar(col=border.color, cex=bottom.right.vp.size), just="left", default.units="native")
	} else {
		grid.text(x=0.1, y=1.65, achievement.label, gp=gpar(col=border.color, cex=bottom.right.vp.size+0.1), just="left", default.units="native")
		grid.text(x=0.1, y=0.55, growth.label, gp=gpar(col=border.color, cex=bottom.right.vp.size+0.1), just="left", default.units="native")
	}
	popViewport()


	###########################
	### Bottom Left Viewport
	###########################

	pushViewport(bottom.left.vp)
	if (is.null(Report_Parameters[['SGP_Targets']])) {
		grid.text(x=0.9, y=1.7, gsub("\\n", " ", scale_score.label), gp=gpar(col=border.color, cex=bottom.left.vp.size), just="right", default.units="native")
		grid.text(x=0.9, y=1.3, achievement_level.label, gp=gpar(col=border.color, cex=bottom.left.vp.size), just="right", default.units="native")
		grid.text(x=0.9, y=0.7, growth_percentile.label, gp=gpar(col=border.color, cex=bottom.left.vp.size), just="right", default.units="native")
		grid.text(x=0.9, y=0.3, growth_level.label, gp=gpar(col=border.color, cex=bottom.left.vp.size), just="right", default.units="native")
	} else {
		grid.text(x=0.9, y=1.95, scale_score.label, gp=gpar(col=border.color, cex=bottom.left.vp.size-0.125), just="right", default.units="native")
		grid.text(x=0.9, y=1.65, achievement_level.label, gp=gpar(col=border.color, cex=bottom.left.vp.size-0.125), just="right", default.units="native")
		grid.text(x=0.9, y=1.35, achievement_target.label, gp=gpar(col=border.color, cex=bottom.left.vp.size-0.125), just="right", default.units="native")
		grid.text(x=0.9, y=0.85, growth_percentile.label, gp=gpar(col=border.color, cex=bottom.left.vp.size-0.125), just="right", default.units="native")
		grid.text(x=0.9, y=0.55, growth_level.label, gp=gpar(col=border.color, cex=bottom.left.vp.size-0.125), just="right", default.units="native")
		grid.text(x=0.9, y=0.25, growth_target.label, gp=gpar(col=border.color, cex=bottom.left.vp.size-0.125), just="right", default.units="native")
	}
	popViewport()


	###################################
	### Right Viewport
	###################################

	if (is.null(Report_Parameters$Assessment_Transition)) {  ##  right.vp without ASSESSMENT_TRANSITION
		pushViewport(right.vp)

		grid.roundrect(width = unit(0.95, "native"), r = unit(.02, "snpc"),
			gp = gpar(lwd = 1.8, col = border.color, fill = legend.fill.color), just = "center")

		if (content.area.in.legend) {
		if (!split.content_area.tf) {
			grid.text(x = 0.5, y = 0.875,
				label = content_area.label.pieces[[1]][1], default.units = "native",
				gp = gpar(col = border.color, cex = content_area.label.pieces[[2]][1],
						  fontface = 2, fontfamily = font.fam))
		} else {
			grid.text(x = 0.5, y = 0.92,
				label = content_area.label.pieces[[1]][1], default.units = "native",
				gp = gpar(col = border.color, cex = content_area.label.pieces[[2]][1],
						  fontface = 2, fontfamily = font.fam))
			grid.text(x = 0.5, y = 0.83,
				label = content_area.label.pieces[[1]][2], default.units = "native",
				gp = gpar(col = border.color, cex = content_area.label.pieces[[2]][2],
						  fontface = 2, fontfamily = font.fam))
		}
		top.offset <- 0
		} else top.offset <- 0.175
		grid.text(x = 0.08, y = (0.75 + top.offset),
			label = achievement.label, default.units = "native", just = "left",
			gp = gpar(col = border.color, cex = 0.85, fontface = 2, fontfamily = font.fam))
		grid.text(x = 0.08, y = 0.525, 
			label = growth.label, default.units = "native", just = "left",
			gp = gpar(col = border.color, cex = 0.75, fontface = 2, fontfamily = font.fam))
		grid.text(x = 0.275, y = 0.455,
			label = level.label, default.units = "native", just = "center",
			gp = gpar(col = border.color, cex = 0.6))
		grid.text(x = 0.75, y = 0.455, 
			label = percentiles.label, default.units = "native", just = "center",
			gp = gpar(col = border.color, cex = 0.6))

		grid.roundrect(x = unit(0.3, "native"), y = unit((0.64 + top.offset), "native"),
			width = unit(0.3, "native"), height = unit(0.1, "native"), r = unit(0.06, "char"),
			gp = gpar(col = "grey72", lwd = 0.4, fill = "grey72"))
		grid.circle(x = 0.3, y = (0.64 + top.offset), r = unit(0.04, "inches"), 
			gp = gpar(col = border.color, lwd = 0.7, fill = "white"), default.units = "native")
		if (!split.content_area.tf) {
			grid.text(x = 0.7, y = (0.675 + top.offset), # just = "top",
				label = paste(test.abbreviation, content.area.label), default.units = "native",
				gp = gpar(col = border.color, cex = legend.size, lineheight = 1/legend.lheight))
			grid.text(x = 0.7, y = (0.625 + top.offset), 
				label = scale_score.label, default.units = "native",
				gp = gpar(col = border.color, cex = legend.size, lineheight = legend.lheight))
		} else {
			grid.text(x = 0.7, y = (0.7 + top.offset), label = test.abbreviation,
				gp = gpar(col = border.color, cex = legend.size), default.units = "native")
			grid.text(x = 0.7, y = (0.665 + top.offset), label = content_area.label.pieces[[1]][1],
				gp = gpar(col = border.color, cex = legend.size), default.units = "native")
			grid.text(x = 0.7, y = (0.63 + top.offset), label = content_area.label.pieces[[1]][2],
				gp = gpar(col = border.color, cex = legend.size), default.units = "native")
			grid.text(x = 0.7, y = (0.595 + top.offset), label = scale_score.label,
				gp = gpar(col = border.color, cex = legend.size, lineheight = legend.lheight), default.units = "native")
		}

		if (extra.legend.element) {
			for (ele in extra.legend.elements) {
				eval(parse(text = ele))
			}
		}

		y.center <- seq(0.05, 0.4, length = number.growth.levels+1)
		arrow.legend.coors.x <- c(0.25, 0.75, 0.75, 1, 0.5, 0, 0.25)
		arrow.legend.coors.y <- c(0, 0, 1.3, 1.1, 2, 1.1, 1.3)
		growth.label.cex <- (0.5 - max(0, max(nchar(growth.level.labels.shortened))-9)*0.01) ### OLD multipler = 0.042

		for (i in seq(number.growth.levels)) {
			pushViewport(
				viewport(x=unit(0.3, "native"), y=unit(y.center[i], "native"),
					width=unit(0.07, "native"), height=unit(y.center[2]-y.center[1], "npc"),
					just=c("center", "bottom"), xscale=c(0, 1), yscale=c(0, 2))
			)
			grid.polygon(x=arrow.legend.coors.x, y=arrow.legend.coors.y, default.units="native",
				gp=gpar(lwd=0.3, col=border.color, fill=arrow.legend.color[i]))
			popViewport()

			grid.polygon(x=c(0.05, rep(0.1875, 2)),
				y=c((head(y.center,1)+tail(y.center,1))/2, y.center[i], y.center[i]+y.center[2]-y.center[1]),
				gp=gpar(col=NA, lwd=0, fill=arrow.legend.color[i], alpha=0.45), default.units="native")
            if (fan.extra) {
                fan.extra.col <- arrow.legend.color[i]
                fan.extra.x <- c(0.05, rep(0.1875, 2))
                fan.extra.y <- c((head(y.center,1)+tail(y.center,1))/2, y.center[i], y.center[i]+y.center[2]-y.center[1])
                eval(parse(text = fan.extras[i]))
            }

			pushViewport(
				viewport(x=unit(0.2, "native"), y=unit(y.center[i], "native"),
					width=unit(0.04, "native"), height=unit(y.center[2]-y.center[1], "npc"),
					just=c("center", "bottom"))
			)
			grid.roundrect(x=0.5, y=0.5, width=1, height=1, r=unit(.45, "snpc"),
				gp=gpar(lwd=0.3, col=border.color, fill=arrow.legend.color[i]))
			popViewport()

			grid.text(x=0.36,
				y=((y.center[1]+y.center[2])/2)+(i-1)*(y.center[2]-y.center[1]),
				label = growth.level.labels.shortened[i], default.units="native",
				gp=gpar(col=border.color, cex=growth.label.cex), just="left")
			grid.text(x=0.925, y=((y.center[1]+y.center[2])/2)+(i-1)*(y.center[2]-y.center[1]),
				label = growth.level.cutscores.text[i], default.units="native",
				gp=gpar(col=border.color, cex=growth.label.cex), just="right")
		}
	} else { ### right.vp with ASSESSMENT_TRANSITION
		pushViewport(right.vp)

		y.boundary.legend.1 <- c(yscale.range[1], yscale.range[1], rep(level_2_1_curve(xscale.range[2]), 2))
		assign(paste0("y.boundary.legend.", number.achievement.level.regions[[2]]),
			c(yscale.range[2], yscale.range[2], rep(eval(parse(text=paste0("level_2", "_", number.achievement.level.regions[[2]]-1, "_curve(xscale.range[2])"))), 2)))

		if (number.achievement.level.regions[[2]] > 2) {
			for (i in 2:(number.achievement.level.regions[[2]]-1)) {
				assign(paste0("y.boundary.legend.", i),
					eval(parse(text=paste0("c(rep(level_2", "_", i-1, "_curve(xscale.range[2]), 2), rep(level_2", "_", i, "_curve(xscale.range[2]), 2))"))))
			}
		}

		for (i in seq(number.achievement.level.regions[[2]])){
		grid.polygon(x = c(0,1,1,0), y = get(paste0("y.boundary.legend.", i)), default.units = "native",
			gp = gpar(fill = achievement.level.region.colors[[2]][i], lwd = 0.5, col = border.color, alpha = 0.7))
		}

		grid.text(label = names(achievement.level.labels[[2]])[1],
			x = 0.06, y = (level_2_1_curve(xscale.range[2]) + yscale.range[1])/2, 
			gp = gpar(col = border.color, fontface = 2, fontfamily = font.fam, cex = .85),
			default.units = "native", just = "left")
		grid.text(label = names(achievement.level.labels[[2]])[number.achievement.level.regions[[2]]],
			x = 0.06,
			y = (eval(parse(text = paste0("level_2", "_", number.achievement.level.regions[[2]]-1, "_curve(xscale.range[2])"))) + yscale.range[2])/2,
			gp = gpar(col = border.color, fontface = 2, fontfamily = font.fam, cex = .85),
			default.units = "native", just = "left")

		if (number.achievement.level.regions[[2]] > 2) {
			for (i in 2:(number.achievement.level.regions[[2]]-1)) {
			grid.text(label = names(achievement.level.labels[[2]])[i],
				x = 0.06,
				y = (eval(parse(text = paste0("(level_2", "_", i-1, "_curve(xscale.range[2]) + level_2", "_", i, "_curve(xscale.range[2]))/2")))),
				gp = gpar(col = border.color, fontface = 2, fontfamily = font.fam, cex = .85),
				default.units = "native", just = "left")
			}
		}

		grid.lines(0, c(yscale.range[1], yscale.range[2]), gp = gpar(lwd = 0.8, col = border.color), default.units = "native")
	}
	popViewport()


	####################################
	### No Data Viewport
	####################################

	if (!grade.values[['any_scale_scores']]) {
		pushViewport(no.data.vp)
		no.data.text <- paste("No", test.abbreviation, content.area.label, "Data")
		no.data.text.pieces <- labelSplit(no.data.text)
		if (length(no.data.text.pieces[[1]])==1) {
			grid.text(x=0.5, y=0.5, no.data.text.pieces[[1]][1], gp=gpar(col="grey40", cex=3.5))
		} else {
			grid.text(x=0.475, y=0.575, no.data.text.pieces[[1]][1], gp=gpar(col="grey40", cex=2.25))
			grid.text(x=0.475, y=0.425, no.data.text.pieces[[1]][2], gp=gpar(col="grey40", cex=2.25))
		}
		grid.roundrect(r=unit(0.02, "snpc"), gp=gpar(fill="white", lwd=0, col="white", alpha=0.5))
		popViewport()
	}

	popViewport()
} ## END studentGrowthPlot Function
CenterForAssessment/SGP documentation built on April 22, 2024, 3:16 p.m.