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 |