Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
plotting.R 22.33 KiB
## Copyright (C) 2020,2021 by University of Luxembourg

## Licensed under the Apache License, Version 2.0 (the "License");
## you may not use this file except in compliance with the License.
## You may obtain a copy of the License at

##     http://www.apache.org/licenses/LICENSE-2.0

## Unless required by applicable law or agreed to in writing, software
## distributed under the License is distributed on an "AS IS" BASIS,
## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
## See the License for the specific language governing permissions and
## limitations under the License.

## Format the y labels.
sci10 <- function(x) {
    prefmt <- formatC(x,format="e",digits=2)
    bits <- strsplit(prefmt,split="e")
    bits1 <-sapply(bits,function(x) {
        if (length(x) > 1) {
            res <- x[[1]]
            sub(" ","~",res)
        } else {
            x
        }
    })
    bits2 <-sapply(bits,function(x) if (length(x)>1) paste0(" %*% 10^","'",sub("[+]"," ",x[[2]]),"'") else "")
    txt <- mapply(function(b1,b2) if (nchar(b2)!=0) {paste0("'",b1,"'",b2)} else NA,
                  bits1,
                  bits2,
                  SIMPLIFY = F)
    names(txt) <- NULL
    txt <- gsub(pattern = "^'0\\.00'.*$","  0",x=txt)
    parse(text=txt)
    
    

}




data4plot_ms1_cgram <- function(tab,select=dtable(adduct=character(0),ID=character(0))) {
    res <- tab[select,.(adduct,tag,ID,rt,intensity),on=c('ID','adduct'),nomatch=NULL]
    if (NROW(res) == 0) return(res)
    data.table::setkeyv(res,c('ID','adduct','tag','rt'))
    res[,max_int:=max(intensity),by=c('ID','adduct','tag')]
    res[,rt_at_max:=rt[which(max_int == intensity)],by=c('ID','adduct','tag')]
    res[,`:=`(lab_id=factor(ID),
              lab_adduct=factor(adduct),
              lab_tag=factor(tag),
              lab_adduct_break = paste0(adduct,":",tag),
              lab_adduct_tag = paste0(adduct,', ',tag))]
    
    
    res
}

data4plot_ms2_cgram <- function(tab,select=dtable(adduct=character(0),ID=character(0))) {
    res <- tab[select,.(adduct,tag,ID,CE,an,rt,intensity),on=c('ID','adduct'),nomatch=NULL]
    if (NROW(res) == 0) return(res)
    data.table::setkeyv(res,c('ID','adduct','tag','CE','an'))
    ## NOTE: I used to not have 'an' in keyby here, but that obviously
    ## generates bad MS2 chromatogram. So, why?
    res <- res[,.(rt,intensity=max(intensity)),keyby=c('ID','adduct','tag','CE','an')]
    res[,`:=`(lab_id=factor(ID),
              lab_adduct=factor(adduct),
              lab_tag=factor(tag),
              lab_adduct_break = paste0(adduct,":",tag))]
    ## Create a table where NCE counts the number of CEs per tab.
    tmp <- res[,.(CE=unique(CE)),keyby=c("ID","adduct","tag")]
    tmp <- tmp[,.(CE=CE,NCE=.N),by=c("ID","adduct","tag")]
    
    tmp[,lab_ce:=fifelse(NCE>0,as.character(CE),NA_character_),by=c("ID","adduct","tag")]
    res <- res[tmp,on=c("ID","adduct","tag","CE")]
    res
}

data4plot_ms2_spec <- function(tab,qatab,select=dtable(adduct=character(0),ID=character(0))) {
    fullkeys <- c('ID','adduct','tag','CE','an')
    select <- qatab[select,on=c('adduct','ID'),nomatch=NULL]
    select <- select[ms2_sel==T,..fullkeys]
    setkeyv(select,fullkeys)
    res <- tab[select,.(adduct,tag,ID,CE,an,rt,mz,intensity),on=fullkeys,nomatch=NULL]
    if (NROW(res) == 0) return(res)
    ## Create a table where NCE counts the number of CEs per tab.
    tmp <- res[,.(CE=unique(CE)),keyby=c("ID","adduct","tag")]
    tmp <- tmp[,.(CE=CE,NCE=.N),by=c("ID","adduct","tag")]
    tmp[,lab_ce:=fifelse(NCE>0,as.character(CE),NA_character_),by=c("ID","adduct","tag")]
    res <- res[tmp,on=c("ID","adduct","tag","CE")]
    res <- res[,.(NCE,lab_ce,rt,mz,intensity),keyby=fullkeys]
    
    res[,`:=`(lab_id=factor(ID),
              lab_adduct=factor(adduct),
              lab_tag=factor(tag),
              lab_adduct_break = paste0(adduct,":",tag),
              lab_adduct_tag = paste0(adduct,', ',tag))]
    res
}



pal_maker <- function(n,palname = NULL) {
    ## The silliest implementation possible. There may be cases when
    ## user requires more than the number of colours accessible in any
    ## ColorBrewer palette. In we need to automatically generate
    ## colours. There are no guarantees the new colours look nice
    ## together with the rest of the palette, so the idea is to fit
    ## the first colours to the original palette, then go over to the
    ## generated ones. Returns a vector of colours.

    krzywinski <- c("#68023F","#008169","#EF0096","#00DCB5",
                    "#FFCFE2","#003C86","#9400E6","#009FFA",
                    "#FF71FD","#7CFFFA","#6A0213","#008607")
    info <- as.data.table(RColorBrewer::brewer.pal.info,keep.rownames = T)
    maxcol <- if (!is.null(palname)) info[rn == palname]$maxcolors else length(krzywinski)
    startpal <- if (!is.null(palname)) RColorBrewer::brewer.pal(maxcol,palname) else krzywinski
    pal <- if (n>length(startpal)) {
               intrppal <-(colorRampPalette(startpal))(n)
               newcol <- setdiff(intrppal,startpal)
               unique(c(startpal,intrppal))
           } else startpal

    pal[1:n]
    
    
}

plot_text <- function(text) {
    theme <- ggplot2::theme_bw() + ggplot2::theme(axis.text.x=ggplot2::element_blank(),
                                                  axis.ticks.x=ggplot2::element_blank(),
                                                  axis.text.y=ggplot2::element_blank(),
                                                  axis.ticks.y=ggplot2::element_blank())
    p <- ggplot2::ggplot(data.frame(x=1:10,y=1:10),
                         ggplot2::aes(x=x,y=y))+
        ggplot2::geom_blank()+ggplot2::labs(x="",y="")

    p <- p + ggplot2::annotate(geom="text",
                               x=5,
                               y=5,
                               size=6,
                               label=text,
                               color="black") + theme

    p
    
}


plot_theme <- function(base_size = 14,
                       panel.grid.major = ggplot2::element_blank(),
                       panel.grid.minor = ggplot2::element_blank(),
                       ...) ggplot2::theme_bw(base_size = base_size) +
                                ggplot2::theme(panel.grid.major=panel.grid.major,
                                               panel.grid.minor=panel.grid.minor)


plot_base <- function(pdata,aes,labs,geom) {
    ggplot2::ggplot(pdata,mapping = aes()) +
        geom() +
        labs +
        plot_theme()
}




plot_palette <- function(pdata) {
    breakslabs  <- pdata[,unique(.SD),.SDcol=c("lab_adduct_break","lab_adduct_tag")]
    breaks <- breakslabs$lab_adduct_break
    labels <- breakslabs$lab_adduct_tag
    values <- pal_maker(length(breaks),palname="Paired")
    names(values) <- breaks
    list(breaks=breaks,
         labels=labels,
         values=values)
}
plot_eic_w_facet <- function(pdata_ms1,pdata_ms2,rt_range,palette) {
    if (NROW(pdata_ms1)==0) return(NULL)
    aes <- function () ggplot2::aes(x=rt,
                                    y = intensity)
    
    aes_ms1 <- function() ggplot2::aes(## y = intensity,
                                       colour = lab_adduct_break)
    aes_ms2 <- if (NROW(pdata_ms2)>0) {
                   if (pdata_ms2[,all(NCE==1)]) {
                       function() ggplot2::aes(colour = lab_adduct_break,
                                               ymin = 0,
                                               ymax = intensity)
                   } else {
                       function() ggplot2::aes(colour = lab_adduct_break,
                                               linetype = lab_ce,
                                               ymin = 0,
                                               ymax = intensity)
                   }
               } else function() ggplot2::aes()

    
    
    scale_colour <- function(name,...) ggplot2::scale_colour_manual(values = palette$values,
                                                                    breaks = palette$breaks,
                                                                    labels = palette$labels,
                                                                    name = name,...)



    pdata_ms1$plottype <- "MS1 EIC"
    pdata_ms2$plottype <- "MS2 EIC"

    labs <- ggplot2::labs(x="retention time [min]",
                          y="intensity")

    
    obj <- ggplot2::ggplot(pdata_ms1, aes()) +
        ggplot2::geom_line(data = pdata_ms1,
                           key_glyph = KEY_GLYPH,
                           aes_ms1())

    if (NROW(pdata_ms2)>0) {
     obj <- obj + ggplot2::geom_linerange(data = pdata_ms2,
                                          aes_ms2()) 
    }

    obj + labs  + scale_y(labels=sci10) + scale_colour(name = "MS1") +
        ggplot2::facet_grid(plottype ~ ID) +
        ggplot2::coord_cartesian(xlim = rt_range) +
        plot_theme()

}


plot_spec_w_facet <- function(pdata_ms2,mz_range,palette) {

    if (NROW(pdata_ms2)==0) return(NULL)
    
    aes_ms2 <- if (NROW(pdata_ms2)>0) {
                   if (pdata_ms2[,all(NCE==1)]) {
                       function() ggplot2::aes(colour = lab_adduct_break,
                                               x = mz,
                                               ymin = 0,
                                               ymax = intensity)
                   } else {
                       function() ggplot2::aes(colour = lab_adduct_break,
                                               linetype = lab_ce,
                                               x = mz,
                                               ymin = 0,
                                               ymax = intensity)
                   }
               } else function() ggplot2::aes()

    breakslabs <- pdata_ms2[,unique(.SD),.SDcol=c("lab_adduct_break","lab_adduct_tag")]
    breaks <-  breakslabs$lab_adduct_break
    labels <- breakslabs$lab_adduct_tag
    
    scale_colour <- function(name,...) ggplot2::scale_colour_manual(values = palette$values,
                                                                    breaks = palette$breaks,
                                                                    labels = palette$labels,
                                                                    name = name,...)
    

    pdata_ms2$plottype <- "MS2 SPECTRA"

    labs <- ggplot2::labs(x="mz",
                          y="intensity")

    

    obj <- if (NROW(pdata_ms2)>0) {
        ggplot2::ggplot(pdata_ms2, aes_ms2()) +
            ggplot2::geom_linerange(key_glyph=KEY_GLYPH) + labs  + scale_y(labels=sci10) +
            scale_colour(name = "MS2") + ggplot2::facet_grid(plottype ~ ID) +
            ggplot2::coord_cartesian(xlim = mz_range) +
            plot_theme()
           } else NULL

    obj

}

## Table legends.

table_eic <- function(pdata) {
    tbl <- pdata[,.(rt=first(rt_at_max)),by=c("ID","adduct","tag")]
    tbl$rt <- format(tbl$rt,digits = 5)
    data.table::setnames(tbl,old = c("adduct","tag","rt"),new = c("Adduct","Tag","RT (MS1) [min]"))
    tbl
}

table_spec <- function(pdata) {
    tbl <- pdata[,.(rt=first(rt)),by=c("ID","adduct","tag","CE")]
    tbl$rt <- format(tbl$rt,digits = 5)
    data.table::setnames(tbl,old = c("adduct","tag","rt"),new = c("Adduct","Tag","RT (MS2) [min]"))
    tbl
}

plot_fname_prefix <- function(decotab,proj_path,subdir=FIG_TOPDIR) {
    if (NROW(decotab)==0) return()
    adducts <- decotab[,adduct]
    ids <- decotab[,ID]
    rpls <- list("\\["="","\\]"="","\\+"="p","\\-"="m")
    fname<-"plot_adduct_"
    for (adduct in adducts) {
        chunk <- adduct
        for (rp in names(rpls)) chunk <- gsub(rp,rpls[[rp]],chunk)
        fname <- paste(fname,chunk,sep = "_")
            
    }
    ddir <- file.path(proj_path,subdir)
    if (!dir.exists(ddir)) dir.create(ddir,recursive = T)
    
    fname <- paste0(fname,"__id_")
    fname <- paste0(fname,paste(ids,collapse = "_"))

    fname <- file.path(ddir,fname)
    fname
    
}

plot_save_single <- function(plot,decotab,extension,proj_path,subdir=FIG_TOPDIR,tabl=NULL,figtag="") {
    if (is.null(plot)) return()
    
    fname <- plot_fname_prefix(decotab,proj_path,subdir=subdir)
    
    fnplot <- paste0(fname,"__",figtag,".",extension)
    
    if (extension == "rds" || extension == "RDS") {
        saveRDS(plot,file=fnplot)
    } else ggplot2::ggsave(filename = fnplot,
                           plot = plot)
    fntab <- paste0(fname,"__",figtag,".csv")
    if (! is.null(tabl)) data.table::fwrite(tabl,file=fntab,sep = ",")
    list(plot=plot,tab=tabl,fn_plot=fnplot)
    
}




## NEW BEGINNINGS


theme_eic <- function(...) theme_light()+ggplot2::theme(axis.title=ggplot2::element_text(size=15L),
                                                        axis.text=ggplot2::element_text(size=12L,colour="black"),
                                                        legend.title=ggplot2::element_text(size=15L),
                                                        plot.title=ggplot2::element_text(size=15L),
                                                        plot.subtitle=ggplot2::element_text(size=12L),
                                                        legend.text=ggplot2::element_text(size=12L),
                                                        plot.caption=ggplot2::element_text(size=12L),
                                                        legend.position='bottom',...)
                               

theme_empty <- ggplot2::theme_bw()
theme_empty$line <- ggplot2::element_blank()
theme_empty$rect <- ggplot2::element_blank()
theme_empty$strip.text <- ggplot2::element_blank()
theme_empty$axis.text <- ggplot2::element_blank()
theme_empty$plot.title <- ggplot2::element_blank()
theme_empty$axis.title <- ggplot2::element_blank()

sci10 <- function(x) {
    prefmt <- formatC(x,format="e",digits=2)
    bits <- strsplit(prefmt,split="e")
    bits1 <-sapply(bits,function(x) {
        if (length(x) > 1) {
            res <- x[[1]]
            sub(" ","~",res)
        } else {
            x
        }
    })
    bits2 <-sapply(bits,function(x) if (length(x)>1) paste0(" %*% 10^","'",sub("[+]"," ",x[[2]]),"'") else "")
    txt <- mapply(function(b1,b2) if (nchar(b2)!=0) {paste0("'",b1,"'",b2)} else NA,
                  bits1,
                  bits2,
                  SIMPLIFY = F)
    names(txt) <- NULL
    txt <- gsub(pattern = "^'0\\.00'.*$","  0",x=txt)
    parse(text=txt)
    
    

}

scale_y<- function (axis="linear", ...) if (axis!="log") {
                                            ggplot2::scale_y_continuous(...)
                                        } else {
                                            ggplot2::scale_y_log10(...)
                                        }


## Concatenates items of a named list in a chain of &-s: x==xval &
## y=yval & z=zval ...)
mk_logic_exp <- function(rest,sofar=NULL) {
    if (length(rest)==0L) {
        return(sofar)
    } else {
        nm = names(rest)[[1]]
        val = rest[[1]]
        ex <- bquote(.(as.symbol(nm)) %in% .(val))
        zz <- if (is.null(sofar)) ex else bquote(.(ex) & .(sofar))
        mk_logic_exp(tail(rest,-1L), zz)
    }
}


get_data_from_key <- function(tab,key) {
    skey <- mk_logic_exp(key)
    eval(bquote(tab[.(skey)]))
}


get_label_group <- function(key) {
    message('CINDEX:',paste0(CINDEX_BY,coll=','))
    message('key:', paste0(key,coll=','))
    setdiff(CINDEX_BY,key)
}

make_line_label <- function(...) {
    paste(...,sep="; ")
}

## Prepare MS1 eic data: rt and intensity of a subset of extracted
## data defined by the key named list. Argument `labs' is a vector of
## names that will be used to construct the legend labels.
get_data_4_eic_ms1 <- function(extr_ms1,kvals,labs) {

    ## Which of the selected keys are in the extr_ms1? This can be
    ## made more obvious to the user, but note necessary atm.
    keys <- names(kvals)
    actual_key <- intersect(keys,names(extr_ms1))
    actual_kvals <- kvals[actual_key]

    ## Subset extr_ms1 by the actual key.
    tab <-get_data_from_key(tab=extr_ms1,key=actual_kvals)

    ## Group the plot data per label group (ie tags, or adducts, or
    ## both).
    xlxx <- intersect(labs,names(extr_ms1))
    xlxx <- as.character(xlxx)
    pdata <- tab[,.(rt,intensity),by=xlxx]
    ## Create labels.
    pdata <- eval(bquote(pdata[,label:=make_line_label(..(lapply(xlxx,as.symbol))),by=xlxx],splice=T))
    setkeyv(pdata,cols=unique(as.character(xlxx),"rt"))
    pdata
}

## Prepare MS2 eic data: rt and intensity + key made of splitby.
get_data_4_eic_ms2 <- function(summ,kvals,labs) {
    tab <-get_data_from_key(tab=summ,key=kvals)
    nms <- names(kvals)
    byby <- unique(c(nms,labs,"an"))
    pdata <- tab[,.(intensity=ms2_int,rt=ms2_rt),by=byby]
    if (NROW(pdata)==0L) return(NULL)
    xlxx <- as.character(labs)
    pdata <- eval(bquote(pdata[,label:=make_line_label(..(lapply(xlxx,as.symbol))),by=.(xlxx)],splice=T))
    setkeyv(pdata,cols=c(labs,"rt"))
    pdata
}


get_rows_from_summ <- function(summ,kvals,...) {
    summ_rows_cols <- c(names(kvals),...)
    get_data_from_key(summ,key=kvals)[,unique(.SD),.SDcol=summ_rows_cols]
}

guide_fun <- function() {
    ggplot2::guides(colour=ggplot2::guide_legend(nrow=2,byrow=T),shape='square')
}
make_eic_ms1_plot <- function(extr_ms1,summ,kvals,labs,axis="linear",rt_range=NULL, asp=1) {

   
    ## Get the table with ms1 data.
    pdata <- get_data_4_eic_ms1(extr_ms1, kvals, labs)
    ## Get metadata.
    summ_rows <- get_rows_from_summ(summ,kvals,"mz","ms1_rt","ms1_int","Name","SMILES","Formula")
    key <- names(kvals)
    ## Deal with retention time range.
    rt_lim <- if (is.null(rt_range)) NULL else ggplot2::xlim(rt_range)
    xrng <- if (!is.null(rt_range)) rt_range else range(pdata$rt)
    dx <- abs(xrng[[2]]-xrng[[1]])
    yrng <- range(pdata$intensity)
    dy <- abs(yrng[[2]]-yrng[[1]])

    ## Calculate aspect ratio.
    aspr <- if (dx < .Machine$double.eps) 1 else asp*as.numeric(dx)/as.numeric(dy)

    tag_txt = paste0(sapply(names(kvals),function (nx) paste0(nx,": ", kvals[[nx]])),
                     collapse='; ') ## paste0("Set: ", set, " ID: ",id)
    title_txt = paste0("MS1 EIC for ion m/z = ",paste0(signif(unique(summ_rows$mz),digits=7L),collapse=", "))
    nm <- paste(unique(summ_rows$Name),collapse="; ")
    subt_txt = if (!length(nm)==0L && !is.na(nm) && nchar(nm)>0L) nm else NULL
    p <- ggplot2::ggplot(pdata,aes(x=rt,y=intensity,colour=label))+ggplot2::labs(caption=tag_txt,title=title_txt,subtitle=subt_txt)+ggplot2::xlab("retention time")+ggplot2::geom_line()+scale_y(axis=axis,labels=sci10)+rt_lim
    annt_dx <- 5*dx/100.
    annt <- summ_rows[,.(x=..annt_dx+ms1_rt,y=ms1_int,txt=signif(ms1_rt,5))]
    ## Annotate.
    p <- p + annotate("text",x=annt$x,y=annt$y,label=annt$txt,size=4,check_overlap=T)+guide_fun()

    ## Add theme.
    p + theme_eic()
}


make_eic_ms2_plot <- function(summ,kvals,labs,axis="linear",rt_range=NULL,asp=1) {
    ## TODO
    ## Get plotting data for the compound.
    pdata <- get_data_4_eic_ms2(summ,
                                kvals=kvals,
                                labs=labs)

    if (NROW(pdata)==0L) return(NULL)

    ## Get metadata.
    summ_rows <- get_rows_from_summ(summ,kvals,"mz","ms2_rt","ms2_int","Name","SMILES","Formula")
    ## Deal with retention time range.
    rt_lim <- if (is.null(rt_range)) NULL else ggplot2::xlim(rt_range)
    xrng <- if (!is.null(rt_range)) rt_range else range(pdata$rt)
    dx <- abs(xrng[[2]]-xrng[[1]])
    yrng <- range(pdata$intensity)
    dy <- abs(yrng[[2]]-yrng[[1]])

    ## Fix aspect ratio.
    aspr <- if (dx < .Machine$double.eps) 1 else asp*as.numeric(dx)/as.numeric(dy)

    ## Derive various labels.
    tag_txt = paste0(sapply(names(kvals),function (nx) paste0(nx,": ", kvals[[nx]])),
                     collapse='; ')
    title_txt = paste0("MS2 EIC for ion m/z = ",paste0(signif(unique(summ_rows$mz),digits=7L),collapse=", "))
    subt_txt = if (!length(summ_rows$Name)==0L && !is.na(summ_rows$Name) && nchar(summ_rows$Name)>0L) summ_rows$Name else NULL
    ## Base plot.
    p <- ggplot2::ggplot(pdata,aes(x=rt,ymin=0,ymax=intensity,colour=label)) +
        ggplot2::labs(caption=tag_txt,title=title_txt,subtitle=subt_txt) +
        ggplot2::xlab("retention time")+ggplot2::ylab("intensity")+ggplot2::geom_linerange()+
        scale_y(axis=axis,labels=sci10)+rt_lim+guide_fun()
    ans <- pdata[,unique(an)]
    annt_dx <- 5*dx/100.
    annt <- summ[an %in% (ans),.(an=an,x=ms2_rt+..annt_dx,y=1.1*ms2_int,txt=signif(ms2_rt,5))]
    ## Annotate.
    p <- p + annotate("text",x=annt$x,y=annt$y,label=annt$txt,size=3,check_overlap=T)

    ## Add theme.
    p + theme_eic()
}


make_spec_ms2_plot <- function(extr_ms2,summ,kvals,labs,axis="linear",asp=1) {

    
    ## Only the chosen ones.
    mdata  <- get_data_from_key(summ,key=kvals)[ms2_sel==T]
    common_key <- intersect(names(extr_ms2),names(kvals))
    common_vals <- kvals[common_key]
    if (length(common_key) == 0L) return(NULL)
    subxdata <- get_data_from_key(extr_ms2,key=common_vals)
    if (NROW(mdata)==0L) return(NULL)
    if (NROW(subxdata) == 0L) return(NULL)
    ans <- data.table(an=mdata[,unique(an)],key="an")
    ms2ctg <- c(intersect(c(names(kvals),labs),names(extr_ms2)),"CE")
    xlxx <- intersect(as.character(labs),names(extr_ms2))
    common_labels <- unique(c("an",common_key,intersect(names(extr_ms2),labs)))
    pdata <- subxdata[ans,on="an"][,.(mz=mz,intensity=intensity,rt=signif(unique(rt),5)),by=common_labels]
    pdata <- eval(bquote(pdata[,label:=make_line_label(..(lapply(c(xlxx,"rt"),as.symbol))),by=.(xlxx)],splice=T))
    pdata <- pdata[,.(mz=mz,intensity=intensity,label=label)]
    if (NROW(pdata)==0L) return(NULL)
    # Aspect ratio.
    xrng <- range(pdata$mz)
    dx <- abs(xrng[[2]]-xrng[[1]])
    yrng <- range(pdata$intensity)
    dy <- abs(yrng[[2]]-yrng[[1]])
    aspr <- if (dx < .Machine$double.eps) 1 else asp*as.numeric(dx)/as.numeric(dy)

    ## Get labels.
    tag_txt = paste0(sapply(names(kvals),function (nx) paste0(nx,": ", kvals[[nx]])),
                     collapse='; ')
    title_txt = paste0("MS2 spectra for ion m/z = ",paste0(signif(unique(mdata$mz),digits=7L),collapse=", "))
    nm <- paste(unique(mdata$Name),collapse="; ")
    subt_txt = if (!length(nm)==0L && !is.na(nm) && nchar(nm)>0L) nm else NULL

    p <- ggplot2::ggplot(pdata,aes(x=mz,ymin=0,ymax=intensity,colour=label))+ggplot2::labs(caption=tag_txt,title=title_txt,subtitle=subt_txt)+ggplot2::xlab("m/z")+ggplot2::geom_linerange()+scale_y(axis=axis,labels=sci10)+guide_fun()

    ## Add theme.
    p + theme_eic()
 
}

combine_plots <- function(p_eic_ms1,p_eic_ms2,p_spec_ms2) {
    cowplot::plot_grid(p_eic_ms1,p_eic_ms2,p_spec_ms2,ncol=1,align='vh',axis='b')
}