# based on http://www.cureffi.org/2013/09/23/a-quick-intro-to-chemical-informatics-in-r/
getRCDKStructurePlot <- function(molecule, width = 500, height = 500, trim = TRUE, transparent = TRUE)
{
# drawing unconnected is not supported. Assume these are salts and we can just draw largest instead
# UNDONE: is this still relevant?
# if (!is.connected(molecule))
# molecule <- get.largest.component(molecule)
img <- rcdk::view.image.2d(molecule, rcdk::get.depictor(width, height)) # get Java representation into an image matrix.
img <- magick::image_read(img)
if (!isEmptyMol(molecule))
{
if (trim)
img <- magick::image_trim(img)
if (transparent)
img <- magick::image_transparent(img, "white")
}
return(img)
}
saveRCDKStructure <- function(molecule, format, out, width = 500L, height = 500L, transparent = TRUE)
{
if (!"fillToFit" %in% formalArgs(rcdk::get.depictor))
{
stopifnot(format == "svg")
# HACK: until https://github.com/CDK-R/cdkr/pull/142 is merged we fallback to some direct CDK hackery
depg <- rJava::.jnew("org/openscience/cdk/depict/DepictionGenerator")
depg <- rJava::.jcall(depg, "Lorg/openscience/cdk/depict/DepictionGenerator;", "withZoom", 1.3)
depg <- rJava::.jcall(depg, "Lorg/openscience/cdk/depict/DepictionGenerator;", "withSize", as.numeric(width),
as.numeric(height))
depg <- rJava::.jcall(depg, "Lorg/openscience/cdk/depict/DepictionGenerator;", "withFillToFit")
depg <- rJava::.jcall(depg, "Lorg/openscience/cdk/depict/DepictionGenerator;", "withAtomColors")
dep <- rJava::.jcall(depg, "Lorg/openscience/cdk/depict/Depiction;", "depict", molecule)
cat(dep$toSvgStr(), file = out)
}
else
{
dep <- rcdk::get.depictor(width, height, style = "cow", fillToFit = TRUE)
mi <- rJava::.jnew("org/guha/rcdk/view/MoleculeImage", molecule, dep)
writeBin(rJava::.jcall(mi, "[B", "getBytes", as.integer(width), as.integer(height), format), out)
}
if (transparent)
{
# HACK: make it transparent
lines <- readLines(out)
lines <- sub("fill='#FFFFFF'", "fill='none'", lines)
writeLines(lines, out)
}
}
getBrewerPal <- function(n, name)
{
maxn <- RColorBrewer::brewer.pal.info[name, "maxcolors"]
if (n > maxn)
return(colorRampPalette(RColorBrewer::brewer.pal(maxn, name))(n))
if (n < 3)
return(RColorBrewer::brewer.pal(3, name)[seq_len(n)])
return(RColorBrewer::brewer.pal(n, name))
}
makeVennPlot <- function(plotObjects, categories, areas, intersectFunc,
intersectLenFunc, ...)
{
nobj <- length(plotObjects)
areas <- unname(areas) # do.call below won't work with names
if (all(areas == 0))
stop("Cannot plot Venn when all objects are empty")
fill <- getBrewerPal(nobj, "Paired")
vennArgs <- list(category = categories, lty = rep("blank", nobj), alpha = rep(0.5, nobj),
cex = 1.5, cat.cex = 1.5, fill = fill)
vennArgs <- modifyList(vennArgs, list(...))
getIntersectCounts <- function(inters) sapply(inters,
function(i) intersectLenFunc(Reduce(intersectFunc, plotObjects[i])))
grid::grid.newpage() # need to clear plot region manually
# plot.new()
if (nobj == 1)
{
gRet <- do.call(VennDiagram::draw.single.venn, c(list(area = areas), vennArgs))
icounts <- numeric()
}
else if (nobj == 2)
{
icounts <- getIntersectCounts(list(c(1, 2)))
gRet <- do.call(VennDiagram::draw.pairwise.venn,
c(areas, icounts, list(rotation.degree = if (areas[1] < areas[2]) 180 else 0), vennArgs))
}
else if (nobj == 3)
{
icounts <- getIntersectCounts(list(c(1, 2), c(2, 3), c(1, 3), c(1, 2, 3)))
gRet <- do.call(VennDiagram::draw.triple.venn, c(areas, icounts, vennArgs))
}
else if (nobj == 4)
{
icounts <- getIntersectCounts(list(c(1, 2), c(1, 3), c(1, 4), c(2, 3), c(2, 4), c(3, 4),
c(1, 2, 3), c(1, 2, 4), c(1, 3, 4), c(2, 3, 4), c(1, 2, 3, 4)))
gRet <- do.call(VennDiagram::draw.quad.venn, c(areas, icounts, vennArgs))
}
else if (nobj == 5)
{
icounts <- getIntersectCounts(list(c(1, 2), c(1, 3), c(1, 4), c(1, 5), c(2, 3), c(2, 4), c(2, 5), c(3, 4),
c(3, 5), c(4, 5), c(1, 2, 3), c(1, 2, 4), c(1, 2, 5), c(1, 3, 4), c(1, 3, 5),
c(1, 4, 5), c(2, 3, 4), c(2, 3, 5), c(2, 4, 5), c(3, 4, 5),
c(1, 2, 3, 4), c(1, 2, 3, 5), c(1, 2, 4, 5), c(1, 3, 4, 5), c(2, 3, 4, 5),
c(1, 2, 3, 4, 5)))
gRet <- do.call(VennDiagram::draw.quintuple.venn, c(areas, icounts, vennArgs))
}
invisible(list(gList = gRet, areas = areas, intersectionCounts = icounts))
}
getMSPlotData <- function(spec, marklwd, markWhich = NULL)
{
hasFragInfo <- !is.null(spec[["ion_formula"]])
plotData <- copy(spec)
# default colour/line width
plotData[, c("colour", "lwd", "legend") := .("grey", 1, "unassigned")]
if (is.null(markWhich))
markWhich <- if (hasFragInfo) which(plotData$annotated) else seq_len(nrow(plotData))
else
markWhich <- plotData[mergedBy %in% markWhich, which = TRUE]
if (!is.null(spec[["mergedBy"]]))
{
plotData[nzchar(mergedBy) & !is.na(mergedBy), legend := sapply(mergedBy, wrapStr, width = 10)]
plotData[markWhich, lwd := marklwd]
mbsUnique <- unique(plotData$legend)
# order from small to big based on number of commas
mbsUnique <- mbsUnique[order(sapply(mbsUnique, countCharInStr, ch = ",", USE.NAMES = FALSE))]
mbCombCols <- setNames(getBrewerPal(length(mbsUnique), "Paired"), mbsUnique)
plotData[, colour := mbCombCols[match(legend, mbsUnique)]]
}
else if (hasFragInfo)
plotData[markWhich, c("colour", "lwd", "legend") := .("blue", marklwd, "assigned")] # nothing merged, just mark all annotated blue
# mark precursor
plotData[precursor == TRUE, c("colour", "lwd", "legend") := .("red", marklwd, "precursor")]
return(plotData)
}
makeScoresPlot <- function(scoreTable, mcn)
{
scores <- setnames(transpose(scoreTable), "score")
scores[, type := names(scoreTable)]
scores <- scores[!is.na(score)]
if (length(mcn) > 1)
{
scores[, merged := "consensus"]
for (n in mcn)
{
withM <- which(grepl(paste0("-", n), scores[["type"]], fixed = TRUE))
set(scores, withM, "merged", n)
set(scores, withM, "type", gsub(paste0("-", n), "", scores[["type"]][withM]))
}
}
isMerged <- length(mcn) > 1 && uniqueN(scores$merged) > 1
oldp <- par(no.readonly = TRUE)
maxStrW <- max(strwidth(unique(scores$type), units = 'in', cex = 0.9)) + 0.5
omai <- par("mai")
par(mai = c(maxStrW, 0.5, omai[3], 0))
if (isMerged)
cols <- getBrewerPal(length(unique(scores$merged)), "Paired")
else
cols <- getBrewerPal(nrow(scores), "Paired")
bpargs <- list(las = 2, col = cols, border = cols, cex.axis = 0.9, xpd = TRUE)
if (isMerged)
{
scSplit <- split(scores, by = "type", keep.by = FALSE)
scSplit <- sapply(names(scSplit), function(mb) setnames(scSplit[[mb]], "score", mb), simplify = FALSE) # assign column names
plotTab <- Reduce(function(left, right)
{
merge(left, right, by = "merged", all = TRUE, sort = FALSE)
}, scSplit)
plot.new()
makeLegend <- function(x, y, ...) legend(x, y, unique(plotTab$merged), col = cols, lwd = 1, xpd = NA, ncol = 1,
cex = 0.75, bty = "n", ...)
# auto legend positioning: https://stackoverflow.com/a/34624632/9264518
leg <- makeLegend(0, 0, plot = FALSE)
lw <- (grconvertX(leg$rect$w, to = "ndc") - grconvertX(0, to = "ndc"))
par(omd = c(0, 1 - lw, 0, 1), new = TRUE)
bpvals <- as.matrix(plotTab[, -"merged"])
bp <- do.call(barplot, c(list(bpvals, beside = TRUE), bpargs))
bpsc <- as.vector(bpvals)
makeLegend(par("usr")[2], par("usr")[4])
}
else
{
bp <- do.call(barplot, c(list(scores$score, names.arg = scores$type), bpargs))
bpsc <- scores$score
}
text(bp, bpsc, labels = round(bpsc, 2), pos = 3, cex = 0.8, xpd = TRUE)
par(oldp)
}
# spec may be annotated
makeMSPlot <- function(plotData, mincex, xlim, ylim, ylab = "Intensity", ..., mol = NULL, maxMolSize, molRes)
{
if (is.null(xlim))
xlim <- range(plotData$mz) * c(0.9, 1.1)
else
{
plotData <- plotData[numGTE(mz, xlim[1]) & numLTE(mz, xlim[2])] # remove any peaks outside plotting range
if (nrow(plotData) == 0)
return(noDataPlot())
}
doLegend <- !is.null(plotData[["legend"]]) && any(!is.na(plotData[["legend"]]) & nzchar(plotData[["legend"]]))
if (doLegend)
{
makeLegend <- function(x, y, ...)
{
legTab <- unique(plotData, by = "legend")[!is.na(legend)]
# order from small to big based on number of commas
legTab <- legTab[order(sapply(legTab$legend, countCharInStr, ch = ",", USE.NAMES = FALSE))]
legend(x, y, legTab$legend, col = legTab$colour, xpd = NA, bty = "n",
text.col = legTab$colour, lty = 1, cex = 0.75, ...)
}
plot.new()
leg <- makeLegend(0, 0, plot = FALSE)
lw <- grconvertX(leg$rect$w, to = "ndc") - grconvertX(0, to = "ndc")
oldp <- par(omd = c(0, 1 - lw, 0, 1), new = TRUE)
}
if (is.null(ylim))
{
expand <- if (!is.null(mol))
1.75
else if (!is.null(plotData[["ion_formula"]]) && any(!is.na(plotData$ion_formula)))
1.5
else
1.2
if (min(plotData$intensity) < 0) # mirror plot?
{
# extend both vertical directions
if (max(plotData$intensity) > 0)
ylim <- range(plotData$intensity) * expand
else # only bottom plot
ylim <- c(min(plotData$intensity), abs(min(plotData$intensity)))
}
else
{
ylim <- if (nrow(plotData) > 1)
range(plotData$intensity)
else
c(0, max(plotData$intensity))
ylim <- ylim * c(1, expand)
}
}
ticks <- pretty(c(-plotData$intensity, plotData$intensity))
plot(0, xlab = "m/z", ylab = ylab, xlim = xlim, ylim = ylim, yaxt = "n", type = "n", bty = "l", ...)
segments(plotData[["mz"]], 0, plotData[["mz"]], plotData[["intensity"]], col = plotData[["colour"]],
lwd = plotData[["lwd"]])
axis(2, at = ticks, labels = abs(ticks))
if (min(plotData$intensity) < 0) # add horizontal line for mirror plots
abline(h = 0, col = "darkgrey")
annPlotData <- NULL
if (!is.null(plotData[["ion_formula"]]))
{
annPlotData <- plotData[!is.na(ion_formula)]
if (nrow(annPlotData) > 0)
{
formsSub <- subscriptFormula(annPlotData$ion_formula)
calcFormHeights <- function(cex)
{
# get widths in inches which are device/direction independent
formWidths <- strwidth(formsSub, cex = cex, units = "inch")
# labels are rotated 90 deg: convert widths to heights
return(yinch(formWidths))
}
curcex <- par("cex")
formHeights <- calcFormHeights(curcex)
totHeights <- abs(annPlotData$intensity) + formHeights
# use formula text height from highest total height
maxFormHeight <- formHeights[which.max(totHeights)]
# how high the text can be depends on ylim (+some spacing)
maxHeight <- min(abs(ylim[1] - min(annPlotData$intensity)),
ylim[2] - max(annPlotData$intensity)) * 0.98
# scale cex if necessary
cex <- max(min(curcex, curcex * (maxHeight / maxFormHeight)), mincex)
if (cex > 0)
{
formHeights <- calcFormHeights(cex) # update with new cex
yoffsets <- ylim[2] * 0.02
yoffsets <- ifelse(annPlotData$intensity < 0, -(yoffsets + formHeights), yoffsets)
text(annPlotData$mz, annPlotData$intensity + yoffsets, formsSub, srt = 90, adj = 0, cex = cex)
annPlotData[, formHeight := formHeights]
}
else
annPlotData[, formHeight := 0]
}
}
if (doLegend)
{
makeLegend(par("usr")[2], par("usr")[4])
par(oldp)
}
# draw structure
if (!is.null(mol))
{
img <- getRCDKStructurePlot(mol[[1]], molRes[1], molRes[2])
dpi <- (par("cra")/par("cin"))[1]
xl <- par("usr")[2]
yl <- par("usr")[4]
imgInfo <- magick::image_info(img)
imgPlotW <- xinch(imgInfo$width / dpi)
imgPlotH <- yinch(imgInfo$height / dpi)
maxW <- maxMolSize[1] * xl
if (imgPlotW > maxW)
{
hresize <- imgPlotW / maxW
imgPlotH <- imgPlotH / hresize
imgPlotW <- maxW
}
# offset a little
startx <- par("usr")[1] + 0.01 * xl
yl <- yl * 0.99
# calculate available height
pd <- plotData[numGTE(mz, startx) & numLTE(mz, startx + imgPlotW)]
availHeight <- ylim[2] - if(nrow(pd) > 0) max(pd$intensity) else 0
if (!is.null(annPlotData))
{
# also take annotations into account
pd <- annPlotData[numGTE(mz, startx) & numLTE(mz, startx + imgPlotW)]
if (nrow(pd) > 0)
availHeight <- min(availHeight, ylim[2] - max(pd$intensity + pd$formHeight))
}
maxH <- min(maxMolSize[2] * yl, availHeight)
if (maxH > 0)
{
if (imgPlotH > maxH)
{
wresize <- imgPlotH / maxH
imgPlotW <- imgPlotW / wresize
imgPlotH <- maxH
}
rasterImage(img, startx, yl - imgPlotH, startx + imgPlotW, yl)
}
}
}
getMSPlotDataOverlay <- function(specs, mirror, normalize, marklwd, markWhich)
{
specs <- lapply(specs, copy)
if (normalize)
specs <- lapply(specs, function(sp) sp[, intensity := normalize(intensity, minMax = FALSE)])
if (mirror && length(specs) == 2)
specs[[2]][, intensity := -intensity]
combinedSpec <- rbindlist(specs, fill = TRUE) # columns may be different due to fragInfos
setorderv(combinedSpec, "intensity")
plotData <- getMSPlotData(combinedSpec, marklwd, markWhich)
return(plotData)
}
makeMSPlotOverlay <- function(plotData, title, mincex, xlim, ylim, ..., mol = NULL, maxMolSize = NULL,
molRes = NULL)
{
makeMSPlot(plotData, mincex, xlim, ylim, ylab = "Normalized intensity",
main = title, ..., mol = mol, maxMolSize = maxMolSize, molRes = molRes)
}
plotDendroWithClusters <- function(dendro, ct, pal, colourBranches, showLegend, ...)
{
if (colourBranches)
{
ct <- ct[order.dendrogram(dendro)] # re-order for dendrogram
nclust <- length(unique(ct[ct != 0])) # without unassigned (in case of dynamicTreeCut)
cols <- getBrewerPal(nclust, pal)
dendro <- dendextend::color_branches(dendro, clusters = ct, col = cols[unique(ct)]) # unique: fixup colour order
lcols <- dendextend::get_leaves_branches_col(dendro)
dendextend::labels_colors(dendro) <- lcols
}
if (colourBranches && showLegend)
{
mr <- par("mar")
mr[4] <- max(5.5, mr[4])
withr::with_par(list(mar = mr),
{
plot(dendro, ...)
legend("topright", legend = seq_len(nclust),
bty = "n", cex = 1, fill = cols, inset = c(-0.22, 0), xpd = NA,
ncol = 2, title = "cluster")
})
}
else
plot(dendro, ...)
}
doPlotSilhouettes <- function(clust, distm, kSeq, pch = 16, type = "b", ...)
{
if (length(distm) == 1)
stop("Need >2 clustered compounds")
meanws <- sapply(kSeq, function(k)
{
sil <- silhouette(cutree(clust, k = k), distm)
summary(sil)$avg.width
})
plot(x = kSeq, y = meanws, pch = pch, type = type, xaxt = "none",
xlab = "cluster k", ylab = "mean silhouette width", ...)
axis(1, kSeq)
abline(v = kSeq[which.max(meanws)], lty = 2)
}
textPlot <- function(txt)
{
withr::with_par(list(mar = c(0, 2, 0, 0)), {
plot(1:10, 1:10, ann = FALSE, xaxt = "n", yaxt = "n", xlab = "", ylab = "", type = "n", adj = 1, bty = "n") # empty dummy plot
text(1, 5, txt, adj = 0, cex = 0.8)
})
}
noDataPlot <- function() textPlot("no data to plot")
doPlotFeatInts <- function(obj, average, normalized, xnames, showLegend, pch, type, lty, col, plotArgs, linesArgs,
doSets)
{
if (xnames && doSets)
{
xnames <- FALSE
warning("xnames option is ignored if sets=TRUE", call. = FALSE)
}
if (length(obj) == 0)
{
noDataPlot()
return(invisible(NULL))
}
snames <- if (average) replicateGroups(obj) else analyses(obj)
tab <- transpose(as.data.table(obj, average = average, normalized = normalized)[, snames, with = FALSE])
if (doSets)
{
anaInfo <- analysisInfo(obj)
anaSets <- if (average) anaInfo[match(replicateGroups(obj), anaInfo$group), "set"] else anaInfo$set
}
if (is.null(col))
{
if (doSets)
col <- colorRampPalette(RColorBrewer::brewer.pal(12, "Paired"))(length(sets(obj)))
else
col <- colorRampPalette(RColorBrewer::brewer.pal(12, "Paired"))(length(tab))
}
oldp <- par(no.readonly = TRUE)
if (showLegend)
{
legN <- if (doSets) sets(obj) else names(obj)
makeLegend <- function(x, y, ...)
{
return(legend(x, y, legN, col = col, pch = pch, text.col = col, xpd = NA, ncol = 1,
cex = 0.75, bty = "n", ...))
}
plot.new()
leg <- makeLegend(0, 0, plot = FALSE)
lw <- (grconvertX(leg$rect$w, to = "ndc") - grconvertX(0, to = "ndc"))
par(omd = c(0, 1 - lw, 0, 1), new = TRUE)
}
maxX <- if (doSets) max(table(anaSets)) else length(snames)
do.call(plot, c(list(x = c(0, maxX), y = c(0, max(tab)), type = "n", xlab = "", ylab = "Intensity", xaxt = "n"),
plotArgs))
if (xnames)
axis(1, seq_len(maxX), snames, las = 2)
else
axis(1, seq_len(maxX), seq_len(maxX))
makeLine <- function(y, col) do.call(lines, c(list(x = seq_along(y), y = y, type = type, pch = pch, lty = lty,
col = col), linesArgs))
for (i in seq_along(tab))
{
if (doSets)
{
for (s in seq_along(sets(obj)))
makeLine(tab[[i]][anaSets == sets(obj)[s]], col[s])
}
else
makeLine(tab[[i]], col[i])
}
if (showLegend)
makeLegend(par("usr")[2], par("usr")[4])
par(oldp)
}
withSVGLite <- withr::with_(function(file, ...) svglite::svglite(file, ...), function(old) dev.off())
doPlotHeaders <- function(obj, what = "tic", retentionRange, MSLevel, retMin = FALSE, title = NULL,
colourBy = c("none", "analyses", "rGroups"), showLegend = TRUE, xlim = NULL, ylim = NULL, ...)
{
ac <- checkmate::makeAssertCollection()
checkmate::assertString(title, null.ok = TRUE, add = ac)
colourBy <- checkmate::matchArg(colourBy, c("none", "analyses", "rGroups"), add = ac)
assertXYLim(xlim, ylim, add = ac)
checkmate::reportAssertions(ac)
if (showLegend && colourBy == "none")
showLegend <- FALSE
if ("tic" %in% what)
data <- getTICs(obj, retentionRange, MSLevel)
else if ("bpc" %in% what)
data <- getBPCs(obj, retentionRange, MSLevel)
else
data <- data.table()
if (nrow(data) == 0)
{
noDataPlot()
return(invisible(NULL))
}
obj <- obj[obj$analysis %chin% unique(data$analysis), ]
anas <- obj$analysis
anaCount <- length(anas)
replicates <- unique(obj$group)
if (colourBy == "rGroups")
{
PlotColors <- colorRampPalette(RColorBrewer::brewer.pal(12, "Paired"))(length(replicates))
names(PlotColors) <- replicates
}
else if (colourBy == "analyses")
{
PlotColors <- colorRampPalette(RColorBrewer::brewer.pal(12, "Paired"))(anaCount)
names(PlotColors) <- anas
}
else
PlotColors <- "blue"
fillColors <- adjustcolor(PlotColors, alpha.f = 0.35)
names(fillColors) <- names(PlotColors)
if (is.null(xlim))
{
xlim <- c(min(data$ret), max(data$ret))
}
if (is.null(ylim))
{
plotIntMax <- max(data$intensity)
ylim <- c(0, plotIntMax * 1.1)
}
if (is.null(title))
{
if (anaCount == 1)
title <- sprintf("Analysis '%s'", anas[1])
else
title <- sprintf("%d analyses", anaCount)
}
if (showLegend)
{
makeLegend <- function(x, y, ...)
{
texts <- if (colourBy == "rGroups") replicates else anas
return(legend(x, y, texts, col = PlotColors[texts],
text.col = PlotColors[texts], lty = 1,
xpd = NA, ncol = 1, cex = 0.75, bty = "n", ...))
}
plot.new()
leg <- makeLegend(0, 0, plot = FALSE)
lw <- (grconvertX(leg$rect$w, to = "ndc") - grconvertX(0, to = "ndc"))
lw <- min(lw, 0.5) # don't make it too wide
withr::local_par(list(omd = c(0, 1 - lw, 0, 1), new = TRUE))
}
plot(0, type = "n", main = title, xlab = sprintf("Retention time (%s)", if (retMin) "min." else "sec."),
ylab = "Intensity", xlim = xlim, ylim = ylim, ...)
effectiveXlim <- par("usr")[c(1, 2)]
effectiveYlim <- par("usr")[c(3, 4)]
for (ana in anas)
{
anadt <- data[analysis %in% ana, ]
if (nrow(anadt) == 0)
next
if (colourBy == "rGroups")
colInd <- obj$group[match(ana, obj$analysis)]
else if (colourBy == "analyses")
colInd <- ana
else
colInd <- 1
points(if (retMin) anadt$ret / 60 else anadt$ret, anadt$intensity, type = "l", col = PlotColors[colInd])
}
if (showLegend)
makeLegend(par("usr")[2], par("usr")[4])
}
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.