<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"><head> <meta charset="utf-8"> <meta name="generator" content="quarto-1.3.353"> <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes"> <meta name="author" content="Michael Love"> <title>Bioconductor cheat sheet</title> <style> code{white-space: pre-wrap;} span.smallcaps{font-variant: small-caps;} div.columns{display: flex; gap: min(4vw, 1.5em);} div.column{flex: auto; overflow-x: auto;} div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;} ul.task-list{list-style: none;} ul.task-list li input[type="checkbox"] { width: 0.8em; margin: 0 0.8em 0.2em -1em; /* quarto-specific, see quarto-dev/quarto-cli#4556 */ vertical-align: middle; } </style> <script src="README_files/libs/clipboard/clipboard.min.js"></script> <script src="README_files/libs/quarto-html/quarto.js"></script> <script src="README_files/libs/quarto-html/popper.min.js"></script> <script src="README_files/libs/quarto-html/tippy.umd.min.js"></script> <script src="README_files/libs/quarto-html/anchor.min.js"></script> <link href="README_files/libs/quarto-html/tippy.css" rel="stylesheet"> <link href="README_files/libs/quarto-html/quarto-syntax-highlighting.css" rel="stylesheet" id="quarto-text-highlighting-styles"> <script src="README_files/libs/bootstrap/bootstrap.min.js"></script> <link href="README_files/libs/bootstrap/bootstrap-icons.css" rel="stylesheet"> <link href="README_files/libs/bootstrap/bootstrap.min.css" rel="stylesheet" id="quarto-bootstrap" data-mode="light"> </head> <body> <div id="quarto-content" class="page-columns page-rows-contents page-layout-article"> <div id="quarto-margin-sidebar" class="sidebar margin-sidebar"> <div class="quarto-alternate-formats"><h2>Other Formats</h2><ul><li><a href="README.pdf"><i class="bi bi-file-pdf"></i>PDF</a></li></ul></div></div> <main class="content" id="quarto-document-content"> <header id="title-block-header" class="quarto-title-block default"> <div class="quarto-title"> <h1 class="title">Bioconductor cheat sheet</h1> </div> <div class="quarto-title-meta"> <div> <div class="quarto-title-meta-heading">Author</div> <div class="quarto-title-meta-contents"> <p>Michael Love </p> </div> </div> </div> </header> <section id="install" class="level2"> <h2 class="anchored" data-anchor-id="install">Install</h2> <p>For details go to http://bioconductor.org/install/</p> <pre><code>if (!requireNamespace("BiocManager")) install.packages("BiocManager") BiocManager::install() BiocManager::install(c("package1","package2") BiocManager::valid() # are packages up to date? # what Bioc version is release right now? http://bioconductor.org/bioc-version # what Bioc versions are release/devel? http://bioconductor.org/js/versions.js</code></pre> </section> <section id="help-within-r" class="level2"> <h2 class="anchored" data-anchor-id="help-within-r">help within R</h2> <p>Simple help:</p> <pre><code>?functionName ?"eSet-class" # classes need the '-class' on the end help(package="foo",help_type="html") # launch web browser help vignette("topic") browseVignettes(package="package") # show vignettes for the package</code></pre> <p>Help for advanced users:</p> <pre><code>functionName # prints source code getMethod(method,"class") # prints source code for method selectMethod(method, "class") # will climb the inheritance to find method showMethods(classes="class") # show all methods for class methods(class="GRanges") # this will work in R >= 3.2 ?"functionName,class-method" # method help for S4 objects, e.g.: ?"plotMA,data.frame-method" # from library(geneplotter) ?"method.class" # method help for S3 objects e.g.: ?"plot.lm" sessionInfo() # necessary info for getting help packageVersion("foo") # what version of package </code></pre> <p>Bioconductor support website: https://support.bioconductor.org</p> <p>If you use RStudio, then you already get nicely rendered documentation using <code>?</code> or <code>help</code>. If you are a command line person, then you can use this alias to pop up a help page in your web browser with <code>rhelp functionName packageName</code>.</p> <pre><code>alias rhelp="Rscript -e 'args <- commandArgs(TRUE); help(args[2], package=args[3], help_type=\"html\"); Sys.sleep(5)' --args"</code></pre> </section> <section id="debugging-r" class="level2"> <h2 class="anchored" data-anchor-id="debugging-r">debugging R</h2> <pre><code>traceback() # what steps lead to an error # debug a function debug(myFunction) # step line-by-line through the code in a function undebug(myFunction) # stop debugging debugonce(myFunction) # same as above, but doesn't need undebug() # also useful if you are writing code is to put # the function browser() inside a function at a critical point # this plus devtools::load_all() can be useful for programming # to jump in function on error: options(error=recover) # turn that behavior off: options(error=NULL) # debug, e.g. estimateSizeFactors from DESeq2... # debugging an S4 method is more difficult; this gives you a peek inside: trace(estimateSizeFactors, browser, exit=browser, signature="DESeqDataSet")</code></pre> </section> <section id="show-package-specific-methods-for-a-class" class="level2"> <h2 class="anchored" data-anchor-id="show-package-specific-methods-for-a-class">Show package-specific methods for a class</h2> <p>These two long strings of R code do approximately the same thing: obtain the methods that operate on an object of a given class, which are defined in a specific package.</p> <pre><code>intersect(sapply(strsplit(as.character(methods(class="DESeqDataSet")), ","), `[`, 1), ls("package:DESeq2")) sub("Function: (.*) \\(package .*\\)","\\1",grep("Function",showMethods(classes="DESeqDataSet", where=getNamespace("DESeq2"), printTo=FALSE), value=TRUE))</code></pre> </section> <section id="annotations" class="level2"> <h2 class="anchored" data-anchor-id="annotations">Annotations</h2> <p>For AnnotationHub examples, see:</p> <p>https://www.bioconductor.org/help/workflows/annotation/Annotation_Resources</p> <p>The following is how to work with the organism database packages, and biomart.</p> <p><a href="http://www.bioconductor.org/packages/release/bioc/html/AnnotationDbi.html">AnnotationDbi</a></p> <pre><code># using one of the annotation packges library(AnnotationDbi) library(org.Hs.eg.db) # or, e.g. Homo.sapiens columns(org.Hs.eg.db) keytypes(org.Hs.eg.db) head(keys(org.Hs.eg.db, keytype="ENTREZID")) # returns a named character vector, see ?mapIds for multiVals options res <- mapIds(org.Hs.eg.db, keys=k, column="ENSEMBL", keytype="ENTREZID") # generates warning for 1:many mappings res <- select(org.Hs.eg.db, keys=k, columns=c("ENTREZID","ENSEMBL","SYMBOL"), keytype="ENTREZID")</code></pre> <p><a href="http://www.bioconductor.org/packages/release/bioc/html/biomaRt.html">biomaRt</a></p> <pre><code># map from one annotation to another using biomart library(biomaRt) m <- useMart("ensembl", dataset = "hsapiens_gene_ensembl") map <- getBM(mart = m, attributes = c("ensembl_gene_id", "entrezgene"), filters = "ensembl_gene_id", values = some.ensembl.genes)</code></pre> </section> <section id="genomic-ranges" class="level2"> <h2 class="anchored" data-anchor-id="genomic-ranges">Genomic ranges</h2> <p><a href="http://bioconductor.org/packages/release/bioc/html/GenomicRanges.html">GenomicRanges</a></p> <pre><code>library(GenomicRanges) z <- GRanges("chr1",IRanges(1000001,1001000),strand="+") start(z) end(z) width(z) strand(z) mcols(z) # the 'metadata columns', any information stored alongside each range ranges(z) # gives the IRanges seqnames(z) # the chromosomes for each ranges seqlevels(z) # the possible chromosomes seqlengths(z) # the lengths for each chromosome</code></pre> <section id="intra-range-methods" class="level3"> <h3 class="anchored" data-anchor-id="intra-range-methods">Intra-range methods</h3> <p>Affects ranges independently</p> <table class="table"> <thead> <tr class="header"> <th>function</th> <th>description</th> </tr> </thead> <tbody> <tr class="odd"> <td>shift</td> <td>moves left/right</td> </tr> <tr class="even"> <td>narrow</td> <td>narrows by relative position within range</td> </tr> <tr class="odd"> <td>resize</td> <td>resizes to width, fixing start for +, end for -</td> </tr> <tr class="even"> <td>flank</td> <td>returns flanking ranges to the left +, or right -</td> </tr> <tr class="odd"> <td>promoters</td> <td>similar to flank</td> </tr> <tr class="even"> <td>restrict</td> <td>restricts ranges to a start and end position</td> </tr> <tr class="odd"> <td>trim</td> <td>trims out of bound ranges</td> </tr> <tr class="even"> <td>+/-</td> <td>expands/contracts by adding/subtracting fixed amount</td> </tr> <tr class="odd"> <td>*</td> <td>zooms in (positive) or out (negative) by multiples</td> </tr> </tbody> </table> </section> <section id="inter-range-methods" class="level3"> <h3 class="anchored" data-anchor-id="inter-range-methods">Inter-range methods</h3> <p>Affects ranges as a group</p> <table class="table"> <thead> <tr class="header"> <th>function</th> <th>description</th> </tr> </thead> <tbody> <tr class="odd"> <td>range</td> <td>one range, leftmost start to rightmost end</td> </tr> <tr class="even"> <td>reduce</td> <td>cover all positions with only one range</td> </tr> <tr class="odd"> <td>gaps</td> <td>uncovered positions within range</td> </tr> <tr class="even"> <td>disjoin</td> <td>breaks into discrete ranges based on original starts/ends</td> </tr> </tbody> </table> </section> <section id="nearest-methods" class="level3"> <h3 class="anchored" data-anchor-id="nearest-methods">Nearest methods</h3> <p>Given two sets of ranges, <code>x</code> and <code>subject</code>, for each range in <code>x</code>, returns…</p> <table class="table"> <colgroup> <col style="width: 50%"> <col style="width: 50%"> </colgroup> <thead> <tr class="header"> <th>function</th> <th>description</th> </tr> </thead> <tbody> <tr class="odd"> <td>nearest</td> <td>index of the nearest neighbor range in subject</td> </tr> <tr class="even"> <td>precede</td> <td>index of the range in subject that is directly preceded by the range in x</td> </tr> <tr class="odd"> <td>follow</td> <td>index of the range in subject that is directly followed by the range in x</td> </tr> <tr class="even"> <td>distanceToNearest</td> <td>distances to its nearest neighbor in subject (Hits object)</td> </tr> <tr class="odd"> <td>distance</td> <td>distances to nearest neighbor (integer vector)</td> </tr> </tbody> </table> <p>A Hits object can be accessed with <code>queryHits</code>, <code>subjectHits</code> and <code>mcols</code> if a distance is associated.</p> </section> <section id="set-methods" class="level3"> <h3 class="anchored" data-anchor-id="set-methods">set methods</h3> <p>If <code>y</code> is a GRangesList, then use <code>punion</code>, etc. All functions have default <code>ignore.strand=FALSE</code>, so are strand specific.</p> <pre><code>union(x,y) intersect(x,y) setdiff(x,y)</code></pre> </section> <section id="overlaps" class="level3"> <h3 class="anchored" data-anchor-id="overlaps">Overlaps</h3> <pre><code>x %over% y # logical vector of which x overlaps any in y fo <- findOverlaps(x,y) # returns a Hits object queryHits(fo) # which in x subjectHits(fo) # which in y </code></pre> </section> <section id="seqnames-and-seqlevels" class="level3"> <h3 class="anchored" data-anchor-id="seqnames-and-seqlevels">Seqnames and seqlevels</h3> <p><a href="http://www.bioconductor.org/packages/release/bioc/html/GenomicRanges.html">GenomicRanges</a> and <a href="http://www.bioconductor.org/packages/release/bioc/html/GenomeInfoDb.html">GenomeInfoDb</a></p> <pre><code>gr.sub <- gr[seqlevels(gr) == "chr1"] seqlevelsStyle(x) <- "UCSC" # convert to 'chr1' style from "NCBI" style '1'</code></pre> </section> </section> <section id="sequences" class="level2"> <h2 class="anchored" data-anchor-id="sequences">Sequences</h2> <p><a href="http://www.bioconductor.org/packages/release/bioc/html/Biostrings.html">Biostrings</a></p> <p>see the <a href="http://www.bioconductor.org/packages/release/bioc/vignettes/Biostrings/inst/doc/BiostringsQuickOverview.pdf">Biostrings Quick Overview PDF</a></p> <p>For naming, see <a href="http://genomicsclass.github.io/book/pages/annoCheat.html">cheat sheet for annotation</a></p> <pre><code>library(BSgenome.Hsapiens.UCSC.hg19) dnastringset <- getSeq(Hsapiens, granges) # returns a DNAStringSet # also Views() for Bioconductor >= 3.1</code></pre> <pre><code>library(Biostrings) dnastringset <- readDNAStringSet("transcripts.fa")</code></pre> <pre><code>substr(dnastringset, 1, 10) # to character string subseq(dnastringset, 1, 10) # returns DNAStringSet Views(dnastringset, 1, 10) # lightweight views into object complement(dnastringset) reverseComplement(dnastringset) matchPattern("ACGTT", dnastring) # also countPattern, also works on Hsapiens/genome vmatchPattern("ACGTT", dnastringset) # also vcountPattern letterFrequecy(dnastringset, "CG") # how many C's or G's # also letterFrequencyInSlidingView alphabetFrequency(dnastringset, as.prob=TRUE) # also oligonucleotideFrequency, dinucleotideFrequency, trinucleotideFrequency # transcribe/translate for imitating biological processes</code></pre> </section> <section id="sequencing-data" class="level2"> <h2 class="anchored" data-anchor-id="sequencing-data">Sequencing data</h2> <p><a href="http://www.bioconductor.org/packages/release/bioc/html/Rsamtools.html">Rsamtools</a> <code>scanBam</code> returns lists of raw values from BAM files</p> <pre><code>library(Rsamtools) which <- GRanges("chr1",IRanges(1000001,1001000)) what <- c("rname","strand","pos","qwidth","seq") param <- ScanBamParam(which=which, what=what) # for more BamFile functions/details see ?BamFile # yieldSize for chunk-wise access bamfile <- BamFile("/path/to/file.bam") reads <- scanBam(bamfile, param=param) res <- countBam(bamfile, param=param) # for more sophisticated counting modes # see summarizeOverlaps() below # quickly check chromosome names seqinfo(BamFile("/path/to/file.bam")) # DNAStringSet is defined in the Biostrings package # see the Biostrings Quick Overview PDF dnastringset <- scanFa(fastaFile, param=granges)</code></pre> <p><a href="http://www.bioconductor.org/packages/release/bioc/html/GenomicAlignments.html">GenomicAlignments</a> returns Bioconductor objects (GRanges-based)</p> <pre><code>library(GenomicAlignments) ga <- readGAlignments(bamfile) # single-end ga <- readGAlignmentPairs(bamfile) # paired-end</code></pre> </section> <section id="transcript-databases" class="level2"> <h2 class="anchored" data-anchor-id="transcript-databases">Transcript databases</h2> <p><a href="http://www.bioconductor.org/packages/release/bioc/html/GenomicFeatures.html">GenomicFeatures</a></p> <pre><code># get a transcript database, which stores exon, trancript, and gene information library(GenomicFeatures) library(TxDb.Hsapiens.UCSC.hg19.knownGene) txdb <- TxDb.Hsapiens.UCSC.hg19.knownGene # or build a txdb from GTF file (e.g. downloadable from Ensembl FTP site) txdb <- makeTranscriptDbFromGFF("file.GTF", format="gtf") # or build a txdb from Biomart (however, not as easy to reproduce later) txdb <- makeTranscriptDbFromBiomart(biomart = "ensembl", dataset = "hsapiens_gene_ensembl") # in Bioconductor >= 3.1, also makeTxDbFromGRanges # saving and loading saveDb(txdb, file="txdb.sqlite") loadDb("txdb.sqlite") # extracting information from txdb g <- genes(txdb) # GRanges, just start to end, no exon/intron information tx <- transcripts(txdb) # GRanges, similar to genes() e <- exons(txdb) # GRanges for each exon ebg <- exonsBy(txdb, by="gene") # exons grouped in a GRangesList by gene ebt <- exonsBy(txdb, by="tx") # similar but by transcript # then get the transcript sequence txSeq <- extractTranscriptSeqs(Hsapiens, ebt)</code></pre> </section> <section id="summarizing-information-across-ranges-and-experiments" class="level2"> <h2 class="anchored" data-anchor-id="summarizing-information-across-ranges-and-experiments">Summarizing information across ranges and experiments</h2> <p>The SummarizedExperiment is a storage class for high-dimensional information tied to the same GRanges or GRangesList across experiments (e.g., read counts in exons for each gene).</p> <pre><code>library(GenomicAlignments) fls <- list.files(pattern="*.bam$") library(TxDb.Hsapiens.UCSC.hg19.knownGene) txdb <- TxDb.Hsapiens.UCSC.hg19.knownGene ebg <- exonsBy(txdb, by="gene") # see yieldSize argument for restricting memory bf <- BamFileList(fls) library(BiocParallel) register(MulticoreParam(4)) # lots of options in the man page # singleEnd, ignore.strand, inter.features, fragments, etc. se <- summarizeOverlaps(ebg, bf) # operations on SummarizedExperiment assay(se) # the counts from summarizeOverlaps colData(se) rowRanges(se)</code></pre> <p>My preferred quantification method is <a href="https://combine-lab.github.io/salmon/">Salmon</a>, with <code>--gcBias</code> option enabled unless you know there is no GC dependence in the data, followed by <a href="http://bioconductor.org/pacakges/tximport">tximport</a>. Here is an example of usage:</p> <pre><code>coldata <- read.table("samples.txt") rownames(coldata) <- coldata$id files <- coldata$files; names(files) <- coldata$id txi <- tximport(files, type="salmon", tx2gene=tx2gene) dds <- DESeqDataSetFromTximport(txi, coldata, ~condition)</code></pre> <p>Another fast Bioconductor read counting method is featureCounts in <a href="http://www.bioconductor.org/packages/release/bioc/html/Rsubread.html">Rsubread</a>.</p> <pre><code>library(Rsubread) res <- featureCounts(files, annot.ext="annotation.gtf", isGTFAnnotationFile=TRUE, GTF.featureType="exon", GTF.attrType="gene_id") res$counts</code></pre> </section> <section id="rna-seq-gene-wise-analysis" class="level2"> <h2 class="anchored" data-anchor-id="rna-seq-gene-wise-analysis">RNA-seq gene-wise analysis</h2> <p><a href="http://www.bioconductor.org/packages/release/bioc/html/DESeq2.html">DESeq2</a></p> <p>My preferred pipeline for DESeq2 users is to start with a lightweight transcript abundance quantifier such as <a href="https://combine-lab.github.io/salmon/">Salmon</a> and to use <a href="http://bioconductor.org/packages/tximport">tximport</a>, followed by <code>DESeqDataSetFromTximport</code>.</p> <p>Here, <code>coldata</code> is a <em>data.frame</em> with <code>group</code> as a column.</p> <pre><code>library(DESeq2) # from tximport dds <- DESeqDataSetFromTximport(txi, coldata, ~ group) # from SummarizedExperiment dds <- DESeqDataSet(se, ~ group) # from count matrix dds <- DESeqDataSetFromMatrix(counts, coldata, ~ group) # minimal filtering helps keep things fast # one can set 'n' to e.g. min(5, smallest group sample size) keep <- rowSums(counts(dds) >= 10) >= n dds <- dds[keep,] dds <- DESeq(dds) res <- results(dds) # no shrinkage of LFC, or: res <- lfcShrink(dds, coef = 2, type="apeglm") # shrink LFCs</code></pre> <p><a href="http://www.bioconductor.org/packages/release/bioc/html/edgeR.html">edgeR</a></p> <pre><code># this chunk from the Quick start in the edgeR User Guide library(edgeR) y <- DGEList(counts=counts,group=group) keep <- filterByExpr(y) y <- y[keep,] y <- calcNormFactors(y) design <- model.matrix(~group) y <- estimateDisp(y,design) fit <- glmFit(y,design) lrt <- glmLRT(fit) topTags(lrt) # or use the QL methods: qlfit <- glmQLFit(y,design) qlft <- glmQLFTest(qlfit) topTags(qlft)</code></pre> <p><a href="http://www.bioconductor.org/packages/release/bioc/html/limma.html">limma-voom</a></p> <pre><code>library(limma) design <- model.matrix(~ group) y <- DGEList(counts) keep <- filterByExpr(y) y <- y[keep,] y <- calcNormFactors(y) v <- voom(y,design) fit <- lmFit(v,design) fit <- eBayes(fit) topTable(fit)</code></pre> <p><a href="http://www.bioconductor.org/packages/release/BiocViews.html#___RNASeq">Many more RNA-seq packages</a></p> </section> <section id="expression-set" class="level2"> <h2 class="anchored" data-anchor-id="expression-set">Expression set</h2> <pre><code>library(Biobase) data(sample.ExpressionSet) e <- sample.ExpressionSet exprs(e) pData(e) fData(e)</code></pre> </section> <section id="get-geo-dataset" class="level2"> <h2 class="anchored" data-anchor-id="get-geo-dataset">Get GEO dataset</h2> <pre><code>library(GEOquery) e <- getGEO("GSE9514")</code></pre> </section> <section id="microarray-analysis" class="level2"> <h2 class="anchored" data-anchor-id="microarray-analysis">Microarray analysis</h2> <pre><code>library(affy) library(limma) phenoData <- read.AnnotatedDataFrame("sample-description.csv") eset <- justRMA("/celfile-directory", phenoData=phenoData) design <- model.matrix(~ Disease, pData(eset)) fit <- lmFit(eset, design) efit <- eBayes(fit) topTable(efit, coef=2)</code></pre> </section> <section id="icobra-performance-metrics" class="level2"> <h2 class="anchored" data-anchor-id="icobra-performance-metrics">iCOBRA performance metrics</h2> <pre><code>library(iCOBRA) cd <- COBRAData(pval=pval.df, padj=padj.df, score=score.df, truth=truth.df) cp <- calculate_performance(cd, binary_truth = "status", cont_truth = "logFC") cobraplot <- prepare_data_for_plot(cp) plot_fdrtprcurve(cobraplot) # interactive shiny app: COBRAapp(cd)</code></pre> </section> </main> <!-- /main column --> <script id="quarto-html-after-body" type="application/javascript"> window.document.addEventListener("DOMContentLoaded", function (event) { const toggleBodyColorMode = (bsSheetEl) => { const mode = bsSheetEl.getAttribute("data-mode"); const bodyEl = window.document.querySelector("body"); if (mode === "dark") { bodyEl.classList.add("quarto-dark"); bodyEl.classList.remove("quarto-light"); } else { bodyEl.classList.add("quarto-light"); bodyEl.classList.remove("quarto-dark"); } } const toggleBodyColorPrimary = () => { const bsSheetEl = window.document.querySelector("link#quarto-bootstrap"); if (bsSheetEl) { toggleBodyColorMode(bsSheetEl); } } toggleBodyColorPrimary(); const icon = ""; const anchorJS = new window.AnchorJS(); anchorJS.options = { placement: 'right', icon: icon }; anchorJS.add('.anchored'); const isCodeAnnotation = (el) => { for (const clz of el.classList) { if (clz.startsWith('code-annotation-')) { return true; } } return false; } const clipboard = new window.ClipboardJS('.code-copy-button', { text: function(trigger) { const codeEl = trigger.previousElementSibling.cloneNode(true); for (const childEl of codeEl.children) { if (isCodeAnnotation(childEl)) { childEl.remove(); } } return codeEl.innerText; } }); clipboard.on('success', function(e) { // button target const button = e.trigger; // don't keep focus button.blur(); // flash "checked" button.classList.add('code-copy-button-checked'); var currentTitle = button.getAttribute("title"); button.setAttribute("title", "Copied!"); let tooltip; if (window.bootstrap) { button.setAttribute("data-bs-toggle", "tooltip"); button.setAttribute("data-bs-placement", "left"); button.setAttribute("data-bs-title", "Copied!"); tooltip = new bootstrap.Tooltip(button, { trigger: "manual", customClass: "code-copy-button-tooltip", offset: [0, -8]}); tooltip.show(); } setTimeout(function() { if (tooltip) { tooltip.hide(); button.removeAttribute("data-bs-title"); button.removeAttribute("data-bs-toggle"); button.removeAttribute("data-bs-placement"); } button.setAttribute("title", currentTitle); button.classList.remove('code-copy-button-checked'); }, 1000); // clear code selection e.clearSelection(); }); function tippyHover(el, contentFn) { const config = { allowHTML: true, content: contentFn, maxWidth: 500, delay: 100, arrow: false, appendTo: function(el) { return el.parentElement; }, interactive: true, interactiveBorder: 10, theme: 'quarto', placement: 'bottom-start' }; window.tippy(el, config); } const noterefs = window.document.querySelectorAll('a[role="doc-noteref"]'); for (var i=0; i<noterefs.length; i++) { const ref = noterefs[i]; tippyHover(ref, function() { // use id or data attribute instead here let href = ref.getAttribute('data-footnote-href') || ref.getAttribute('href'); try { href = new URL(href).hash; } catch {} const id = href.replace(/^#\/?/, ""); const note = window.document.getElementById(id); return note.innerHTML; }); } let selectedAnnoteEl; const selectorForAnnotation = ( cell, annotation) => { let cellAttr = 'data-code-cell="' + cell + '"'; let lineAttr = 'data-code-annotation="' + annotation + '"'; const selector = 'span[' + cellAttr + '][' + lineAttr + ']'; return selector; } const selectCodeLines = (annoteEl) => { const doc = window.document; const targetCell = annoteEl.getAttribute("data-target-cell"); const targetAnnotation = annoteEl.getAttribute("data-target-annotation"); const annoteSpan = window.document.querySelector(selectorForAnnotation(targetCell, targetAnnotation)); const lines = annoteSpan.getAttribute("data-code-lines").split(","); const lineIds = lines.map((line) => { return targetCell + "-" + line; }) let top = null; let height = null; let parent = null; if (lineIds.length > 0) { //compute the position of the single el (top and bottom and make a div) const el = window.document.getElementById(lineIds[0]); top = el.offsetTop; height = el.offsetHeight; parent = el.parentElement.parentElement; if (lineIds.length > 1) { const lastEl = window.document.getElementById(lineIds[lineIds.length - 1]); const bottom = lastEl.offsetTop + lastEl.offsetHeight; height = bottom - top; } if (top !== null && height !== null && parent !== null) { // cook up a div (if necessary) and position it let div = window.document.getElementById("code-annotation-line-highlight"); if (div === null) { div = window.document.createElement("div"); div.setAttribute("id", "code-annotation-line-highlight"); div.style.position = 'absolute'; parent.appendChild(div); } div.style.top = top - 2 + "px"; div.style.height = height + 4 + "px"; let gutterDiv = window.document.getElementById("code-annotation-line-highlight-gutter"); if (gutterDiv === null) { gutterDiv = window.document.createElement("div"); gutterDiv.setAttribute("id", "code-annotation-line-highlight-gutter"); gutterDiv.style.position = 'absolute'; const codeCell = window.document.getElementById(targetCell); const gutter = codeCell.querySelector('.code-annotation-gutter'); gutter.appendChild(gutterDiv); } gutterDiv.style.top = top - 2 + "px"; gutterDiv.style.height = height + 4 + "px"; } selectedAnnoteEl = annoteEl; } }; const unselectCodeLines = () => { const elementsIds = ["code-annotation-line-highlight", "code-annotation-line-highlight-gutter"]; elementsIds.forEach((elId) => { const div = window.document.getElementById(elId); if (div) { div.remove(); } }); selectedAnnoteEl = undefined; }; // Attach click handler to the DT const annoteDls = window.document.querySelectorAll('dt[data-target-cell]'); for (const annoteDlNode of annoteDls) { annoteDlNode.addEventListener('click', (event) => { const clickedEl = event.target; if (clickedEl !== selectedAnnoteEl) { unselectCodeLines(); const activeEl = window.document.querySelector('dt[data-target-cell].code-annotation-active'); if (activeEl) { activeEl.classList.remove('code-annotation-active'); } selectCodeLines(clickedEl); clickedEl.classList.add('code-annotation-active'); } else { // Unselect the line unselectCodeLines(); clickedEl.classList.remove('code-annotation-active'); } }); } const findCites = (el) => { const parentEl = el.parentElement; if (parentEl) { const cites = parentEl.dataset.cites; if (cites) { return { el, cites: cites.split(' ') }; } else { return findCites(el.parentElement) } } else { return undefined; } }; var bibliorefs = window.document.querySelectorAll('a[role="doc-biblioref"]'); for (var i=0; i<bibliorefs.length; i++) { const ref = bibliorefs[i]; const citeInfo = findCites(ref); if (citeInfo) { tippyHover(citeInfo.el, function() { var popup = window.document.createElement('div'); citeInfo.cites.forEach(function(cite) { var citeDiv = window.document.createElement('div'); citeDiv.classList.add('hanging-indent'); citeDiv.classList.add('csl-entry'); var biblioDiv = window.document.getElementById('ref-' + cite); if (biblioDiv) { citeDiv.innerHTML = biblioDiv.innerHTML; } popup.appendChild(citeDiv); }); return popup.innerHTML; }); } } }); </script> </div> <!-- /content --> </body></html>