

polar2Cartesian = function(d) {
    theta = d[, 1]/180*pi
    rou = d[, 2]
    x = rou * cos(theta)
    y = rou * sin(theta)
    return(cbind(x, y))
}

calc_n_neighbours_on_circle = function(theta, width = 1) {
	od = order(order(theta))
	theta2 = sort(theta)

	k = cpp_calc_n_neighbours(theta2, width)
	k[od]
}

default_edge_transparency = function(dag) {
	n_relations = sum(sapply(dag@lt_children, length))
	if(n_relations < 3000) {
		0.5
	} else if(n_relations > 50000) {
		0.95
	} else {
		(0.95-0.5)/(50000 - 3000)*(n_relations - 5000) + 0.5
	}
}

#' Visualize the DAG
#' 
#' @param dag An `ontology_Dag` object.
#' @param highlight A vector of terms to be highlighted on the DAG.
#' @param start Start of the circle, measured in degree.
#' @param end End of the circle, measured in degree.
#' @param partition_by_level If `node_col` is not set, users can cut the DAG into clusters with different node colors. The partitioning is applied by [`partition_by_level()`].
#' @param partition_by_size Similar as `partition_by_level`, but the partitioning is applied by [`partition_by_size()`].
#' @param node_col Colors of nodes. If the value is a vector, the order should correspond to terms in [`dag_all_terms()`].
#' @param node_transparency Transparency of nodes. The same format as `node_col`.
#' @param node_size Size of nodes. The same format as `node_col`.
#' @param edge_col A named vector where names correspond to relation types.
#' @param edge_transparency A named vector where names correspond to relation types.
#' @param legend_labels_from If partitioning is applied on the DAG, a legend is generated showing different top
#'         terms. By default, the legend labels are the term IDs. If there are additionally column stored
#'         in the meta data frame of the DAG object, the column name can be set here to replace the term IDs as
#'         legend labels.
#' @param legend_labels_max_width Maximal width of legend labels measured by the number of characters per line. Labels are wrapped into
#'        multiple lines if the widths exceed it.
#' @param other_legends A list of legends generated by [`ComplexHeatmap::Legend()`].
#' @param use_raster Whether to first write the circular image into a temporary png file, then add to the plot
#'      as a raster object?
#' @param newpage Whether call [`grid::grid.newpage()`] to create a new plot?
#' @param verbose Whether to print messages.
#' 
#' @details
#' `dag_circular_viz()` uses a circular layout for visualizing large DAGs. `dag_graphviz()`
#' uses a hierarchical layout for visualizing small DAGs.
#' 
#' @import grid
#' @importFrom Polychrome alphabet.colors
#' @import ComplexHeatmap
#' @importFrom grDevices dev.size dev.off png
#' @rdname dag_viz
#' @export
#' @examples
#' \donttest{
#' dag = create_ontology_DAG_from_GO_db()
#' dag_circular_viz(dag)
#' }
#' 1
dag_circular_viz = function(dag, highlight = NULL, 
	start = 0, end = 360,
	partition_by_level = 1, partition_by_size = NULL,
	node_col = NULL, node_transparency = 0.4, node_size = NULL, 
	edge_col = NULL, edge_transparency = default_edge_transparency(dag),
	legend_labels_from = NULL, legend_labels_max_width = 50, other_legends = list(),
	use_raster = dag_n_terms(dag) > 10000, newpage = TRUE,
	verbose = simona_opt$verbose) {

	if(use_raster) {
		check_pkg("png", bioc = FALSE)
	}

	if(end < start) {
		stop_wrap("`end` should be larger than `start`.")
	}

	n_terms = dag@n_terms

	has_highlight = FALSE
	if(!is.null(highlight)) {
		has_highlight = TRUE
		l_highlight = dag@terms %in% highlight
	}

	if(dag_is_tree(dag)) {
		tree = dag
	} else {
		if(verbose) message("converting DAG to a tree...")
		tree = dag_treelize(dag, verbose = verbose)
	}
	if(verbose) message("calculating term positions on the DAG...")	
	term_pos = exec_under_message_condition({
		cpp_node_pos_in_tree(tree, n_connected_leaves(tree), start, end) ## in polar coordinate
	}, verbose = verbose)

	# if(!dag_is_tree(dag) && !test) {  ## reordering is only applied when DAG is not a tree
	# 	lt_counterpart = cpp_get_force_counterpart(dag@lt_children, dag@lt_parents, tree@lt_children, tree@lt_parents, dag@root)

	# 	theta = cpp_reorder_tree_x(tree, lt_counterpart, term_pos$x, term_pos$width, times)
	# 	term_pos$x = theta
	# }

	# term_pos$n_neighbours = 0
	# all_levels = sort(unique(term_pos$h))
	# all_levels = all_levels[!is.na(all_levels)]
	# for(level in all_levels) {
	# 	l = term_pos$h == level
	# 	if(verbose) message(strrep("\b", 100), appendLF = FALSE)
	# 	if(verbose) message(qq("calculating numbers of neighbours within 1 degree neighbourhood on level @{level}/@{max(all_levels)}, @{sum(l)} terms..."), appendLF = FALSE)
	# 	term_pos[l, "n_neighbours"] = cpp_calc_n_neighbours(term_pos$x[l], width = 0.5)
	# }
	# if(verbose) message("")

	node_col_map = NULL
	l_uncolored_nodes = NULL
	if(is.null(node_col)) {
		if(is.null(partition_by_size)) {
			group = partition_by_level(dag, level = partition_by_level, term_pos = term_pos)
		} else {
			group = partition_by_size(dag, size = partition_by_size)
		}
		level1 = unique(group); level1 = level1[!is.na(level1)]
		n_levels = length(level1)
		
		ind = which( dag@terms %in% level1)
		level1 = dag@terms[ind][order(term_pos[ind, "x"])]

		if(n_levels <= 7) {
			default_col = c("#DF536B", "#61D04F", "#2297E6", "#28E2E5", "#CD0BBC", "#F5C710", "#9E9E9E")
			node_col_map = default_col[seq_len(n_levels)]
		} else if(n_levels <= 26) {
			node_col_map = alphabet.colors(n_levels)
		} else {
			node_col_map = rand_color(n_levels)
		}

		names(node_col_map) = level1
		node_col = node_col_map[as.character(group)]
		l_uncolored_nodes = unname(is.na(node_col))
		node_col[is.na(node_col)] = "#CCCCCC"
	}

	if(is.null(node_size)) {
		if(has_highlight) {
			node_size = rep(8, n_terms)
			node_size_by_n_children = FALSE
		} else {
			n_children = dag@term_env$n_children
			node_size = .scale(n_children, c(0, quantile(n_children, 0.99)+1), c(2, 10))
			node_size_by_n_children = TRUE
		}
	} else {
		node_size_by_n_children = FALSE
	}

	if(!length(node_col) %in% c(1, n_terms)) {
		stop_wrap(qq("Length of `node_col` should be one or @{n_terms} (number of terms in the DAG)."))
	}
	if(!length(node_transparency) %in% c(1, n_terms)) {
		stop_wrap(qq("Length of `node_transparency` should be one or @{n_terms} (number of terms in the DAG)."))
	}
	if(!length(node_size) %in% c(1, n_terms)) {
		stop_wrap(qq("Length of `node_size` should be one or @{n_terms} (number of terms in the DAG)."))
	}

	# node_transparency = .scale(term_pos$n_neighbours, c(1, quantile(term_pos$n_neighbours, 0.9)+1), c(0.9, 0.5))

	node_col = add_transparency(node_col, node_transparency)

	if(length(dag@lt_children_relations) > 0) {
		relation_levels = attr(dag@lt_children_relations, "levels")
		v_relations = unlist(dag@lt_children_relations)
		v_relations = relation_levels[v_relations]
		n_levels = length(relation_levels)

		if(is.null(edge_col)) {
			if(n_levels <= 8) {
				default_col = c("#000000", "#DF536B", "#61D04F", "#2297E6", "#28E2E5", "#CD0BBC", "#F5C710", "#9E9E9E")
				edge_col = structure(default_col[seq_len(n_levels)], names = relation_levels)
			} else if(n_levels <= 26) {
				edge_col = structure(alphabet.colors(n_levels), names = relation_levels)	
			} else {
				edge_col = structure(rand_color(n_levels), names = relation_levels)
			}
		} else if(is.atomic(edge_col)) {
			if(length(edge_col) == 1) {
				edge_col = structure(rep(edge_col, n_levels), names = relation_levels)
			}
		}

		if(length(setdiff(names(edge_col), relation_levels))) {
			stop("names in `edge_col` should cover all relation types.")
		}

		if(is.atomic(edge_transparency)) {
			if(length(edge_transparency) == 1) {
				edge_transparency = structure(rep(edge_transparency, n_levels), names = relation_levels)
			}
		}

		if(!is.null(names(edge_transparency))) {
			if(length(setdiff(names(edge_transparency), relation_levels))) {
				stop("names in `edge_transparency` should cover all relation types.")
			}
			edge_transparency = edge_transparency[v_relations]
		}

		if(length(l_uncolored_nodes)) {
			l_uncolored_edges = rep(l_uncolored_nodes, times = sapply(dag@lt_children, length))
			edge_transparency = ifelse(l_uncolored_edges, pmax(0.96, edge_transparency), edge_transparency)
		}

		edge_col_v = add_transparency(edge_col[v_relations], edge_transparency)
	} else {

		if(length(l_uncolored_nodes)) {
			l_uncolored_edges = rep(l_uncolored_nodes, times = sapply(dag@lt_children, length))
			edge_transparency = ifelse(l_uncolored_edges, pmax(0.96, edge_transparency), edge_transparency)
		}
		if(is.null(edge_col)) {
			edge_col_v = "black"
		}
		edge_col_v = add_transparency(edge_col_v, edge_transparency)
	}

	if(has_highlight) {
		node_col[!l_highlight] = add_transparency("black", 0.95)
		node_col[l_highlight] = add_transparency(node_col[l_highlight], 0)
		node_size[!l_highlight] = 2
	}
	
	lt_children = dag@lt_children
	n = dag@n_terms
	df_edge = data.frame(from = rep(seq_len(n), times = vapply(lt_children, length, FUN.VALUE = integer(1))),
		                 to = unlist(lt_children))
	# df_edge2 = data.frame(from = rep(seq_len(n), times = vapply(tree@lt_children, length, FUN.VALUE = integer(1))),
	# 	                 to = unlist(tree@lt_children))
	# adjust rho
	tb = log(table(term_pos$h)+1); tb = tb[names(tb) != "0"]
	rho2 = cumsum(tb)/sum(tb); rho2["0"] = 0
	term_pos$h = rho2[as.character(term_pos$h)]

	term_pos2 = polar2Cartesian(term_pos) ## xy coordinate

	x1 = term_pos2[df_edge$from, 1]
	y1 = term_pos2[df_edge$from, 2]
	x2 = term_pos2[df_edge$to, 1]
	y2 = term_pos2[df_edge$to, 2]

	# x10 = term_pos2[df_edge2$from, 1]
	# y10 = term_pos2[df_edge2$from, 2]
	# x20 = term_pos2[df_edge2$to, 1]
	# y20 = term_pos2[df_edge2$to, 2]

	max_depth = max(abs(term_pos$h))

	if(verbose) message("making plot...")

	lgd_list = list()
	if(!is.null(node_col_map)) {

		if(has_highlight) {
			node_col_map = node_col_map[intersect(names(node_col_map), group[l_highlight])]
		} else {
			sector_width = term_pos[vapply(names(node_col_map), function(x) which(dag@terms == x), FUN.VALUE = integer(1)), "width"]
			if(sum(sector_width > 1) > 0) {
				node_col_map = node_col_map[sector_width > 1]
			}
		}
		# if(length(node_col_map) > 20) {
		# 	n_offspring = n_offspring(dag)
		# 	node_col_map = node_col_map[ order(-n_offspring[names(node_col_map)])[seq_len(20)] ]
		# }
		if(is.null(legend_labels_from)) {
			if("name" %in% colnames(mcols(dag))) {
				legend_labels = mcols(dag)[names(node_col_map), "name"]
			} else {
				legend_labels = names(node_col_map)
			}
		} else {
			legend_labels = mcols(dag)[names(node_col_map), legend_labels_from]
		}
		legend_labels = vapply(legend_labels, function(x) {
			x = strwrap(x, width = legend_labels_max_width)
			if(length(x) > 1) {
				x[-1] = paste0("  ", x[-1])
			}
			paste(x, collapse = "\n")
		}, FUN.VALUE = character(1))
		ii = which(is.na(legend_labels) | legend_labels == "NA")
		if(length(ii)) {
			legend_labels[ii] = names(node_col_map)[ii]
		}

		lgd_list = c(lgd_list, list(Legend(title = "Top terms", labels = legend_labels,
			type = "points", pch = 16,
			background = add_transparency(node_col_map, 0.9, FALSE), 
			legend_gp = gpar(col = node_col_map))))
	}
	if(!is.null(edge_col)) {
		legend_labels = names(edge_col)
		legend_labels = vapply(legend_labels, function(x) {
			x = strwrap(x, width = legend_labels_max_width)
			if(length(x) > 1) {
				x[-1] = paste0("  ", x[-1])
			}
			paste(x, collapse = "\n")
		}, FUN.VALUE = character(1))
		lgd_list = c(lgd_list, list(Legend(title = "Relations", labels = legend_labels, type = "lines", legend_gp = gpar(col = edge_col))))
	}
	if(node_size_by_n_children) {
		n_children = dag@term_env$n_children
		lgd_breaks = grid.pretty(c(1, quantile(n_children, 0.99)+1), n = 3)
		lgd_node_size = .scale(lgd_breaks, c(0, quantile(n_children, 0.99)+1), c(2, 10))
		lgd_labels = lgd_breaks
		if(max(lgd_breaks) != max(n_children)) {
			lgd_labels[length(lgd_labels)] = paste0(">= ", max(lgd_breaks), ", max = ", max(n_children))
		}
		lgd_list = c(lgd_list, list(Legend(title = "Number of child terms", labels = lgd_labels, type = "points", size = unit(lgd_node_size, "pt"))))
	}

	if(length(other_legends)) {
		if(inherits(other_legends, "Legend")) {
			lgd_list = c(lgd_list, list(other_legends))
		} else {
			lgd_list = c(lgd_list, other_legends)
		}
	}

	if(length(lgd_list)) {
		lgd = packLegend(list = lgd_list)
		lgd_width = width.Legends(lgd)
	} else {
		lgd_width = unit(0, "pt")
	}
	if(newpage) {
		grid.newpage()
	}

	# legend
	if(length(lgd_list)) {
		pushViewport(viewport(x = unit(1, "npc"), width = lgd_width + unit(4, "pt"), just = "right"))
		draw(lgd, x = unit(0, "npc"), just = "left")
		popViewport()
	}

	pushViewport(viewport(x = unit(0, "npc"), width = unit(1, "npc") - lgd_width - unit(4, "pt"), just = "left"))

	if(use_raster) {
		if(verbose) {
			message("saving the circular plot into a temporary png file...")
		}
		temp_png = tempfile()
		vp_width = convertWidth(unit(1, "snpc") - unit(8, "pt"), "in", valueOnly = TRUE)
		vp_height = convertHeight(unit(1, "snpc") - unit(8, "pt"), "in", valueOnly = TRUE)
		if(vp_width < 1) {
			vp_width = vp_height = 1
		}
		png(temp_png, width = vp_width*1.5, height = vp_height*1.5, units = "in", res = 72*1.5)
	}
	oe = try({
		pushViewport(viewport(width = unit(1, "snpc") - unit(8, "pt"), height = unit(1, "snpc") - unit(8, "pt"), 
			xscale = c(-max_depth, max_depth), yscale = c(-max_depth, max_depth)))
		if(!is.null(node_col_map)) {
			for(nm in names(node_col_map)) {
				i = which(dag@terms == nm)
				draw_sector(term_pos[i, "x"] - term_pos[i, "width"]/2,
					        term_pos[i, "x"] + term_pos[i, "width"]/2,
					        max_depth, gp = gpar(fill = add_transparency(node_col_map[[nm]], 0.9, FALSE), col = NA))
			}
		}

		theta = seq(0, pi*2, length = 50)
		for(i in unique(term_pos$rho)) {
			grid.lines(cos(theta)*i, sin(theta)*i, default.units = "native", gp = gpar(col = "#CCCCCC", lty = 3))
		}

		if(has_highlight) {
			grid.segments(x1, y1, x2, y2, default.units = "native", gp = gpar(col = edge_col_v))
			grid.points(term_pos2[!l_highlight, 1], term_pos2[!l_highlight, 2], default.units = "native", pch = 16, 
				gp = gpar(col = node_col[!l_highlight]), size = unit(node_size[!l_highlight], "pt"))
			grid.points(term_pos2[l_highlight, 1], term_pos2[l_highlight, 2], default.units = "native", pch = 16, 
				gp = gpar(col = node_col[l_highlight]), size = unit(node_size[l_highlight], "pt"))
		} else {
			if(verbose) message("adding links...")
			# grid.segments(x1, y1, x2, y2, default.units = "native", gp = gpar(col = "red"))
			grid.segments(x1, y1, x2, y2, default.units = "native", gp = gpar(col = edge_col_v))
			if(verbose) message("adding terms...")
			grid.points(term_pos2[, 1], term_pos2[, 2], default.units = "native", pch = 16, 
				gp = gpar(col = node_col), size = unit(node_size, "pt"))
			# grid.text(1:nrow(term_pos2), term_pos2[, 1], term_pos2[, 2], default.units = "native")
		}

		popViewport()
	}, silent = TRUE)

	if(use_raster) {
		dev.off()

		image = png::readPNG(temp_png)
		file.remove(temp_png)

		grid.raster(image, width = unit(1, "snpc") - unit(8, "pt"), height = unit(1, "snpc") - unit(8, "pt"))

	}

	if(inherits(oe, "try-error")) {
		stop(oe)
	}

	popViewport()

	if(verbose) {
		ds = dev.size()
		w = unit(ds[2], "in") + lgd_width + unit(4, "pt")
		w = convertWidth(w, "in", valueOnly = TRUE)

		message(qq("Best device size: @{round(w, 2)} x @{round(ds[2], 2)} inches."))
	}

}

.scale = function(v, range, map) {
	v[v > range[2]] = range[2]
	v[v < range[1]] = range[1]

	(map[2] - map[1])/(range[2] - range[1])*(v - range[1]) + map[1]
}

.normalize_node_graphics_parameter = function(value, default, all_terms, name) {
	n = length(all_terms)
	if(!is.null(names(value))) {
		value2 = rep(default, n)
		names(value2) = all_terms

		cn = intersect(names(value), names(value2))
		if(length(cn) == 0) {
			stop_wrap(qq("Since `@{name}` is a named vector, the names should correspond to the term names in the DAG."))
		}
		value2[cn] = value[cn]

		value = unname(value2)
	}

	if(!length(value) %in% c(1, n)) {
		stop_wrap(qq("Length of `@{name}` should be one or @{n} (number of terms in the DAG)."))
	}

	value
}

.normalize_edge_graphics_parameter = function(value, default, parents, children, v_relations, name) {
	n = length(parents)

	# name can use the relation type or "parent -> child"
	if(!is.null(names(value))) {
		value2 = rep(default, n)

		lt = strsplit(names(value), " +<?->? +")
		len = sapply(lt, length)

		l = len == 1
		if(any(l)) {
			for(i in which(l)) {
				value2[ v_relations == names(value)[i] ] = value[i]
			}
		}

		for(i in which(!l)) {
			value2[ parents == lt[[i]][1] & children == lt[[i]][2] ] = value[i]
			value2[ children == lt[[i]][1] & parents == lt[[i]][2] ] = value[i]
		}

		value = value2
	}

	if(!length(value) %in% c(1, n)) {
		stop_wrap(qq("Length of `@{name}` should be one or @{n} (number of edges in the DAG)."))
	}

	value
}


default_node_param = list(
	color = "black", 
	fillcolor = "lightgrey", 
	style = "solid",
	fontcolor = "black", 
	fontsize = 10, 
	shape = "box"
)

default_edge_param = list(
	color = "black",
	style = "solid",
	dir = "back"
)

#' @param node_param A list of parameters. Each parameter has the same format. The value can be
#'      a single scalar, a full length vector with the same order as in [`dag_all_terms()`],
#'             or a named vector that contains a subset of terms that need to be customized. 
#'        The full set of parameters can be found at \url{https://graphviz.org/docs/nodes/}.
#' @param edge_param A list of parameters. Each parameter has the same format. The value can be a single
#'     scalar, or a named vector that contains a subset of terms that need to be customized. 
#'        The full set of parameters can be found at \url{https://graphviz.org/docs/edges/}.
#'     If the parameter is set to a named vector, it can be named by relation types `c("is_a" = ...)`,
#'     or directly relations `c("a -> b" = ...)`. Please see the vignette for details.
#' @param rankdir The direction of the layout. Only four values are allowed: `"TB"`, `"LR"`, `"BT"` and `"RL"`.
#'
#' @seealso \url{http://magjac.com/graphviz-visual-editor/} is nice place to try the DOT code.
#' @details `dag_as_DOT()` generates the DOT code of the DAG.
#' @importFrom circlize rand_color
#' @export
#' @returns
#' `dag_as_DOT()` returns a vector of DOT code.
#' @rdname dag_viz
dag_as_DOT = function(dag, node_param = default_node_param,
	edge_param = default_edge_param, rankdir = c("TB", "LR", "BT", "RL")) {

	for(nm in names(default_node_param)) {
		if(is.null(node_param[[nm]])) {
			node_param[[nm]] = default_node_param[[nm]]
		}
	}
	for(nm in names(node_param)) {
		if(is.null(default_node_param[[nm]])) {
			node_param[[nm]] = .normalize_node_graphics_parameter(node_param[[nm]], NA, dag@terms, nm)
		} else {
			node_param[[nm]] = .normalize_node_graphics_parameter(node_param[[nm]], default_node_param[[nm]], dag@terms, nm)
		}
	}

	name = NULL
	if(!is.null(mcols(dag))) {
		meta = mcols(dag)
		if("name" %in% colnames(meta)) {
			name = meta[, "name"]
			name = gsub("\"", "\\\\\"", name)
			name[is.na(name)] = ""
		}
	}

	n_nodes = dag_n_terms(dag)
	nodes = "  node [fontname = \"Helvetical\"]"
	for(i in seq_len(n_nodes)) {
		nodes = c(nodes, qq("  \"@{dag@terms[i]}\" ["))
	
		for(nm in names(node_param)) {
			v = node_param[[nm]]
			if(length(v) != 1) {
				v = v[i]
			}
			if(!is.na(v)) {
				nodes = c(nodes, qq("    @{nm} = \"@{v}\","))
			}
		}
		if(!is.null(name)) {
			nodes = c(nodes, qq("    tooltip = \"@{name[i]}\","))
		}
		nodes = c(nodes, "  ];")
	}
	
	lt_children = dag@lt_children
	children = unlist(lt_children)
	parents = rep(seq_along(dag@terms), times = vapply(lt_children, length, FUN.VALUE = integer(1)))
	children = dag@terms[children]
	parents = dag@terms[parents]

	if(length(dag@lt_children_relations) > 0) {
		relation_levels = attr(dag@lt_children_relations, "levels")
		v_relations = unlist(dag@lt_children_relations)
		v_relations = relation_levels[v_relations]
		n_levels = length(relation_levels)

	} else {
		v_relations = NULL
	}

	for(nm in names(default_edge_param)) {
		if(is.null(edge_param[[nm]])) {
			edge_param[[nm]] = default_edge_param[[nm]]
		}
	}
	for(nm in names(edge_param)) {
		if(is.null(default_edge_param[[nm]])) {
			edge_param[[nm]] = .normalize_edge_graphics_parameter(edge_param[[nm]], NA, parents, children, v_relations, nm)
		} else {
			edge_param[[nm]] = .normalize_edge_graphics_parameter(edge_param[[nm]], default_edge_param[[nm]], parents, children, v_relations, nm)
		}
	}

	n_edges = length(children)
	edges = "  # edges"
	for(i in seq_len(n_edges)) {
		edges = c(edges, qq("  \"@{parents[i]}\" -> \"@{children[i]}\" ["))
	
		for(nm in names(edge_param)) {
			v = edge_param[[nm]]
			if(length(v) != 1) {
				v = v[i]
			}
			if(!is.na(v)) {
				edges = c(edges, qq("    @{nm} = \"@{v}\","))
			}
		}
		if(!is.null(v_relations)) {
			edges = c(edges, qq("    tooltip = \"@{v_relations[i]}\","))
		}
		edges = c(edges, "  ];")
	}

	rankdir = match.arg(rankdir)[1]
	DOT = c(
		"digraph {",
		"  graph [",
	 qq("    rankdir = \"@{rankdir}\","),
		"    overlap = true",
		"  ]",
		"",
		nodes,
		"",
		edges,
		"}",
		""
	)

	dot = paste(DOT, collapse = "\n")
	class(dot) = "print_source"
	dot
}

#' Print the source
#' 
#' @param x An object in the `print_source` class.
#' @param ... Other arguments.
#' 
#' @details
#' Internally used.
print.print_source = function(x, ...) {
	cat(x)
}

#' @param ... Pass to [`DiagrammeR::grViz()`].
#' @rdname dag_viz
#' @details
#' `dag_graphviz()` visualizes the DAG with the **DiagrammeR** package.
#' @export
#' @examples
#' if(interactive()) {
#' dag = create_ontology_DAG_from_GO_db()
#' dag_graphviz(dag[, "GO:0010228"])
#' dag_graphviz(dag[, "GO:0010228"], 
#'     edge_param = list(color = c("is_a" = "purple", "part_of" = "darkgreen"),
#'                       style = c("is_a" = "solid", "part_of" = "dashed")),
#'     width = 800, height = 800)
#' 
#' # the DOT code for graphviz
#' dag_as_DOT(dag[, "GO:0010228"])
#' }
dag_graphviz = function(dag, 
	node_param = default_node_param, edge_param = default_edge_param, 
	rankdir = "TB", ...) {

	if(dag@n_terms > 100) {
		warning("graphviz is only efficient for visualizing small graphs.")
	}
	if(dag@n_terms > 500) {
		stop("Too many terms.")
	}
	check_pkg("DiagrammeR", bioc = FALSE)

	dot = dag_as_DOT(dag, node_param = node_param, edge_param = edge_param, rankdir = rankdir)
	DiagrammeR::grViz(dot, ...)
}

# center at (0, 0)
draw_sector = function(start, end, radius, ...) {
	if(end < start) {
		end = end + 360
	}

	theta = c(0, seq(start, end, by = 0.5), 0)
	rho = c(0, rep(radius, length(theta)-2), 0)
	
	pos = polar2Cartesian(cbind(theta, rho))
	grid.polygon(pos[, 1], pos[, 2], default.units = "native", ...)	
}
