Use a QP solver to find the best places to put the points on a line, subject to the constraint that they should not overlap.
qp.labels <- structure(function# Make a Positioning Method for non-overlapping lineplot labels ### Use a QP solver to find the best places to put the points on a ### line, subject to the constraint that they should not overlap. (target.var, ### Variable name of the label target. lower.var, ### Variable name of the lower limit of each label bounding box. upper.var, ### Variable name of the upper limit of each label bounding box. order.labels=function(d)order(d[,target.var]), ### Function that takes the data.frame of labels and returns an ### ordering, like from the order function. That ordering will be used ### to reorder the rows. This is useful to e.g. break ties when two ### groups have exactly the same value at the endpoint near the label. limits=NULL ### Function that takes the data.frame of labels an returns a numeric ### vector of length 2. If finite, these values will be used to add ### constraints to the QP: limits[1] is the lower limit for the first ### label's lower.var, and limits[2] is the upper limit for the last ### labels's upper.var. Or NULL for no limits. ){ ## Reality checks. These also have the side effect of forcing ## evaluation of all the arguments in the returned closure. stopifnot(is.function(order.labels)) essential <- list(target.var,upper.var,lower.var) for(v in essential){ stopifnot(is.character(v)) stopifnot(length(v)==1) } stopifnot(is.function(limits)||is.null(limits)) function(d,...){ ## If there is only 1 label, there is no collision detection to ## do, so just return it. if(nrow(d)==1)return(d) ## Reality checks. for(v in essential){ if(! v %in% names(d)){ stop("need to have calculated ",v) } } ## sorts data so that target_1 <= target_2 <= ... <= target_n. d <- d[order.labels(d),] ## check limits to see if there is enough space, given specified ## cex. if(is.function(limits)){ l <- limits(d) stopifnot(is.numeric(l)) stopifnot(length(l)==2) stopifnot(l[1]<l[2]) h.available <- l[2] - l[1] h <- d[,upper.var]-d[,lower.var] h.occupied <- sum(h) if(h.occupied > h.available){ ## then the feasible set is empty. ## total hack: cex <- h.available / h.occupied * 0.9 if("cex" %in% names(d)){ d$cex <- d$cex * cex }else{ d$cex <- cex } d <- calc.boxes(d) } } ## These are the standard form matrices described in the ## directlabels poster. target <- d[,target.var] k <- nrow(d) D <- diag(rep(1,k)) Ik <- diag(rep(1,k-1)) A <- rbind(0,Ik)-rbind(Ik,0) y.up <- d[,upper.var] y.lo <- d[,lower.var] b0 <- (y.up-target)[-k] + (target-y.lo)[-1] ## limit constraints. if(is.function(limits)){ if(is.finite(l[1])){ c.vec <- rep(0,k) c.vec[1] <- 1 A <- cbind(A,c.vec) b0 <- c(b0,l[1]+target[1]-y.lo[1]) } if(is.finite(l[2])){ c.vec <- rep(0,k) c.vec[k] <- -1 A <- cbind(A,c.vec) b0 <- c(b0,y.up[k]-target[k]-l[2]) } } ##print(A) ##print(b0) ##browser() sol <- solve.QP(D,target,A,b0) d[,target.var] <- sol$solution d } ### Positioning Method that adjusts target.var so there is no overlap ### of the label bounding boxes, as specified by upper.var and ### lower.var. },ex=function(){ SegCost$error <- factor(SegCost$error,c("FP","FN","E","I")) library(ggplot2) fp.fn.colors <- c(FP="skyblue",FN="#E41A1C",I="black",E="black") fp.fn.sizes <- c(FP=2.5,FN=2.5,I=1,E=1) fp.fn.linetypes <- c(FP="solid",FN="solid",I="dashed",E="solid") err.df <- subset(SegCost,type!="Signal") if(!"theme"%in%ls("package:ggplot2")){ theme <- opts } kplot <- ggplot(err.df,aes(segments,cost))+ geom_line(aes(colour=error,size=error,linetype=error))+ facet_grid(type~bases.per.probe)+ scale_linetype_manual(values=fp.fn.linetypes)+ scale_colour_manual(values=fp.fn.colors)+ scale_size_manual(values=fp.fn.sizes)+ scale_x_continuous(limits=c(0,20),breaks=c(1,7,20),minor_breaks=NULL)+ theme_bw()+theme(panel.margin=unit(0,"lines")) ## The usual ggplot without direct labels. print(kplot) ## Get rid of legend for direct labels. no.leg <- kplot+guides(colour="none",linetype="none",size="none") ## Default direct labels. direct.label(no.leg) ## Explore several options for tiebreaking and limits. First let's ## make a qp.labels Positioning Method that does not tiebreak. no.tiebreak <- list("first.points", "calc.boxes", qp.labels("y","bottom","top")) direct.label(no.leg, no.tiebreak) ## Look at the weird labels in the upper left panel. The E curve is ## above the FN curve, but the labels are the opposite! This is ## because they have the same y value on the first points, which are ## the targets for qp.labels. We need to tiebreak. qp.break <- qp.labels("y","bottom","top",make.tiebreaker("x","y")) tiebreak <- list("first.points", "calc.boxes", "qp.break") direct.label(no.leg, tiebreak) ## Enlarge the text size and spacing. tiebreak.big <- list("first.points", cex=2, "calc.boxes", dl.trans(h=1.25*h), "calc.borders", "qp.break") direct.label(no.leg, tiebreak.big) ## Even on my big monitor, the FP runs off the bottom of the screen ## in the top panels. To avoid that you can specify a limits ## function. ## Below, the ylimits function uses the limits of each panel, so ## labels appear inside the plot region. Also, if you resize your ## window so that it is small, you can see that the text size of the ## labels is decreased until they all fit in the plotting region. qp.limited <- qp.labels("y","bottom","top",make.tiebreaker("x","y"),ylimits) tiebreak.lim <- list("first.points", cex=2, "calc.boxes", dl.trans(h=1.25*h), "calc.borders", "qp.limited") direct.label(no.leg, tiebreak.lim) })
Please contact Toby Dylan Hocking if you are using directlabels or have ideas to contribute, thanks! |
Documentation website generated from source code version 2021.2.24 (git revision bb6db07 Mon, 14 Jun 2021 22:38:45 +0530) using inlinedocs. |
validate |