diff --git a/data/SC.rds b/data/SC.rds new file mode 100644 index 0000000..47097e9 Binary files /dev/null and b/data/SC.rds differ diff --git a/plottingReference.R b/plottingReference.R new file mode 100644 index 0000000..30e1e0f --- /dev/null +++ b/plottingReference.R @@ -0,0 +1,1349 @@ +# tocID <- "plottingReference.R" +# +# Purpose: Reference to graphical output in R. +# +# +# Version: 2.0 +# +# Date: 2017 09 - 2020 10 +# Author: Boris Steipe (boris.steipe@utoronto.ca) +# +# V 2.0 Comprehensive reference with basic and advanced options based on +# an integrated yeast gene expression dataset. Full rewrite of most +# sections. +# V 1.1 Stylistic improvements, code polish, and additional examples +# V 1.0 Digest from "plotting reference" files +# +# ToDo: +# +# + + +#TOC> ========================================================================== +#TOC> +#TOC> Section Title Line +#TOC> ------------------------------------------------------------------ +#TOC> 01 INITIALIZE 67 +#TOC> 02 THIS REFERENCE ... 72 +#TOC> 02.1 Dataset Documentation 79 +#TOC> 03 PROPORTIONS AND DISTRIBUTIONS 191 +#TOC> 03.1 barplot() 196 +#TOC> 03.2 pie() 220 +#TOC> 03.3 boxplot() 252 +#TOC> 03.4 hist() 310 +#TOC> 03.4.1 overlaying histograms 355 +#TOC> 04 THE plot() FUNCTION 398 +#TOC> 04.1 line plots 402 +#TOC> 05 ENCODING INFORMATION: SYMBOL, SIZE, COLOR 501 +#TOC> 05.1 pch ("plotting character" symbols) 507 +#TOC> 05.1.1 Line types 682 +#TOC> 06 COLOUR 704 +#TOC> 06.1 Colours by number 712 +#TOC> 06.2 Colours by name 730 +#TOC> 06.3 Colours as hex-triplets 756 +#TOC> 06.3.1 Inbuilt palettes 811 +#TOC> 06.3.2 Custom palettes 887 +#TOC> 06.3.3 Transparency: The Alpha Channel 937 +#TOC> 06.4 abline(), lines() and segments() 983 +#TOC> 07 AXES 1017 +#TOC> 08 LEGENDS 1054 +#TOC> 08.1 basic legends 1057 +#TOC> 08.2 Color bars 1061 +#TOC> 09 LAYOUT 1168 +#TOC> 10 TEXT 1203 +#TOC> 11 DRAWING ON PLOTS 1224 +#TOC> 12 IMAGES 1291 +#TOC> 13 CONTOUR LINES 1296 +#TOC> 14 3D PLOTS 1301 +#TOC> 15 GRAPHS AND NETWORKS 1306 +#TOC> 16 OTHER GRPAHICS PACKAGES 1311 +#TOC> 17 INTERACTIVE PLOTS 1335 +#TOC> 17.1 locator() 1339 +#TOC> 17.2 plotly:: 1342 +#TOC> +#TOC> ========================================================================== + + +# = 01 INITIALIZE ========================================================= + +SC <- readRDS("data/SC.rds") # <<<- execute this line first + + +# = 02 THIS REFERENCE ... ================================================= + +# This script covers basic plotting and graphical output options of R. The +# functions are demonstrated with an integrated data set of yeast gene +# expression data and annotations. + + +# == 02.1 Dataset Documentation ============================================ + +# You do not need to study the dataset in detail but it will be useful to refer +# to this documentation from time to time to understand what data is being +# plotted and what one can therefore learn from the results. + +# The integrated dataset SC is a list that contains an analysis of yeast gene +# expression profiles originally published by Pramila et al. (2002; PMID: +# 12464633) and accessible as GSE3653 on GEO +# (https://www.ncbi.nlm.nih.gov/geo/query/acc.cgi?acc=GSE3635). This is a +# high-resolution (5 min. interval) data set spanning two cycles of a +# synchronized yeast culture. It includes 6228 expression profiles with 25 +# time-points assigned to yeast systematic names. It is complemented with gene +# annotations taken from SGD, and augmented with a set of GO slims. GO slims +# were rerieved by navigating to https://www.yeastgenome.org/ and using the menu +# to choose Function >> Gene Ontology >> GOslim Mapping File. This downloads +# go_slim_mapping.tab which was further processed to annotate each gene with its +# corresponding GO terms. To evaluate cyclical regulation, a curve-fitting +# protocol was followed to fit a cyclical epxpression model with parameters +# amplitude, phase, frequency, exponential damping, and baseline shift. After +# fitting the profiles, correlations with the fitted model were calculated and +# the first peak of the model was determined as a marker of when the expression +# was ON in the cell-cycle. Finally, the expression peak markers were assigned +# to seven phases of the cell cycle, and GO term enrichments were computed. A +# A subset was selected with the following parameters: + +# selCCl <- +# nlsParams$A > 0.075 & # reasonably high Amplitude +# nlsParams$cor > 0.6 & # good correlation with the parametrized model +# nlsParams$f > 0.75 & # period between 0.75 ... +# nlsParams$f < 1.333 & # ... and 1.333 hours +# nlsParams$k < 0.03 & # limiting the exponential damping +# nlsParams$k > -0.001 +# sum(selCCl) # 1,297 genes in the data set show some level +# # of periodic expression-variation in the cell +# # cycle. +# +# Thus the dataset comprises numeric and categorical data +# Here are the data details: +# SC$xpr : expression profiles. Numeric matrix with 1,297 rows of genes +# ====== and 25 colums of time points in 5 minute +# intervals +# Example: +# -------- +# SC$xpr[169, 1:5] +# > t.0 t.5 t.10 t.15 t.20 +# > -0.055703429 -0.116124955 -0.135965733 -0.068665035 -0.001819363 +# +# +# SC$mdl : parametrized model. Data frame with 1,297 rows of genes +# ====== +# SC$mdl$A : model parameter: amplitude (log ratio) +# SC$mdl$phi : model parameter: phase (degrees) +# SC$mdl$f : model parameter: 1/frequency (hours) +# SC$mdl$k : model parameter: damping +# SC$mdl$B : model parameter: baseline shift +# SC$mdl$cor : correlation of observation and model +# SC$mdl$peaks : timepoint of first expression peak of model (minutes) +# +# # Example: +# -------- +# SC$mdl[169, ] +# > A phi f k B cor peaks +# > 0.1007908 -81.04413 1.012092 0.00656052 -0.01857916 0.8437175 37.52217 +# +# +# SC$ann : annotations. Data frame with 1,297 rows of genes +# ====== +# SC$ann$SGD : Saccharomyces Gene Database identifier +# SC$ann$sysName : Yeast gene systematic name +# SC$ann$stdName : The standard name under which the gene is known +# SC$ann$alias : common alias name(s) +# SC$ann$description : free-text description of the gene +# SC$ann$GO : GO IDs of the SGD GO slim subset annotated to the gene +# +# Example: +# -------- +# SC$ann[169, ] +# > SGD sysName stdName alias +# > S000002214 YDL056W MBP1 transcription factor MBP1 +# > description +# > YDL056W Transcription factor; involved in regulation [...] +# > GO +# > YDL056W GO:0005634 GO:0003677 GO:0001071 GO:0000278 +# +# SC$phases +# --------- +# Data frame with a definition of yeast cell cycle phase names and +# time-points derived from inspection of the SC$mdl$peaks distribution. +# names start ends +# 1 Sense 0 6 +# 2 Prep 6 15 +# 3 Replicate 15 30 +# 4 Assemble 30 37 +# 5 Segregate 37 43 +# 6 Duplicate 43 55 +# 7 Stabilize 55 65 +# +# SC$GO +# ----- +# Data frame with GO term information and GO term enrichment data +# SC$GO$ontology : {C|F|P} identifying the "cellular component", "molecular +# function" or "biological process" ontology +# SC$GO$GOid : Gene Ontyology ID of the term +# SC$GO$label : Short label of the term +# SC$GO$tAll : Count of times the term is annotated in any gene +# SC$GO$tCC : ... times term is annotated to a gene in this dataset +# SC$GO$t... : ... annotated to one ofg the cell-cycle phases +# SC$GO$xs... : ... enrichment factor in each cell cycle phase +# + + +# = 03 PROPORTIONS AND DISTRIBUTIONS ====================================== + +# Distributions of numeric variable characterize the values. Proportions compare values. A typical use case is to characterize a set of measurements, or compare several sets of measurements. + + +# == 03.1 barplot() ======================================================== +# Draws a bar with height proportional to a given number. + +barplot(mean(SC$xpr[ , "t.20"])) # barplot of the mean expression at t.20 + +# Barplots show only a single number. They tell us nothing about the underlying distribution. They are commonly used to compare several distributions: + +barplot(colMeans(SC$xpr[ , c("t.0", "t.20", "t.40", "t.60")])) + +# barplots take the usual plotting parameters +myPal <- colorRampPalette(c("#00FF0055", "#0066FF55", # a "palette" function + "#00FFFF55", "#CCCCAA55", + "#FF00FF55", "#FF006655", + "#FFFFFF55"), alpha = TRUE) # transparent colors + +barplot(colMeans(SC$xpr[ , 1:13]), # mean expression changes, first hour + main = "Mean expression changes", + xlab = "time points", + cex.names = 0.6, # scale column names + ylab = "Expression (log ratio)", + cex.axis = 0.8, # scale of y-axis labels + col = myPal(13)) # get 13 color values from myPal() + + +# == 03.2 pie() ============================================================ +# Generally not preferred, but simple to do + +table(SC$GO$ontology) +# Example: how many genes are anotated to the GO terms in the Cellular Component +# category? +CCids <- SC$GO$GOid[SC$GO$ontology == "C"] # get GO ids for "C" ontology +x <- unlist(strsplit(SC$ann$GO, " ")) # split GO term strings on blank-spaces + # and dump them all into one single vector +x <- x[x %in% CCids] # subset the ones in CCIDs (about 40%) + +pie(table(x), cex = 0.5) + +# This can be better visualized in a barplot +myT <- sort(table(x), decreasing = TRUE) +b <- barplot(myT, # assign the plot - we need the x-coordinates + main = "Genes annotated to GO terms in the Cellular Component Ontology", + cex.main = 0.8, + names.arg = "", # blank the names - otherwise they are + ylab = "counts", # taken from the names of the object + cex.axis = 0.8, + col = colorRampPalette(c("#CCAAFF", "#FFDDFF", "#FFFFFF"))(length(myT)), + ylim = c(0, 1.5 * max(myT))) # make space at the top + +# Add the GOids as text() +text(b, myT, # b holds the x coordinates, myT is y + labels = names(myT), # take the names from the table - names + srt = 90, # rotate to vertical + adj = c(-0.1, 0.5), # align center, top + cex = 0.5) + + +# == 03.3 boxplot() ======================================================== +# A boxplot() is almost always preferred to a barplot, since it includes an +# estimate of the distribution: + +boxplot(SC$xpr[ , "t.20"]) # boxplot of expression values at t.20 + +# What are these elements? +# Assigning the plot to a variable makes the numbers available so we can +# use them for annotation: +oPar <- par(mar = c(0.2,3,0.2,0.2)) # reduce the margins +( b <- boxplot(SC$xpr[ , "t.20"])) + +xT <- 1.3 +cT <- "#DD0000" +# outliers +myOut <- mean(b$out[b$out > b$stats[5]]) # outliers above the whiskers +myOut[2] <- mean(b$out[b$out < b$stats[1]]) # outliers below the whiskers +text(1.04, myOut, adj=c(0,0.5), cex=0.7, col=cT, "outliers") + +# whiskers +text(1.12, b$stats[1,1], adj=c(0,0.5), cex=0.7, col=cT, "min(x_i > Q1-1.5*IQR)") +text(1.12, b$stats[5,1], adj=c(0,0.5), cex=0.7, col=cT, "max(x_i < Q3+1.5*IQR)") + +# quartiles +# "min(x_i > Q1-1.5*IQR)" is "the smallest x in the data that still lies +# within 1.5 times the inter-quartile range below Q1 +text(1.23, b$stats[2,1], adj=c(0,0.5), cex=0.7, col=cT, "Q1 (lower quartile)") +text(1.23, b$stats[4,1], adj=c(0,0.5), cex=0.7, col=cT, "Q3 (upper quartile)") + +# median +text(1.23, b$stats[3,1], adj=c(0,0.5), cex=0.9, col=cT, "median") + +# the mean is not shown +text(0.79, mean(SC$xpr[,"t.20"]), adj=c(1,0.5), cex=0.7, col="#999999", "mean") + +par(oPar) # reset the margins + +# If we have datasets with more columns, each column gets its own boxplot +boxplot(SC$xpr[ , c("t.0", "t.20", "t.40", "t.60")]) + +# boxplots take the usual plotting parameters +myPal <- colorRampPalette(c("#00FF0055", "#0066FF55", # a "palette" function + "#00FFFF55", "#CCCCAA55", + "#FF00FF55", "#FF006655", + "#FFFFFF55"), alpha = TRUE) # transparent colors + +boxplot(SC$xpr[ , 1:13], # expression changes, first hour + main = "Expression levels", + xlab = "time points", + cex.axis = 0.6, # scale axis names + ylab = "Expression (log ratio)", + col = myPal(13)) # get 13 color values from myPal() + +# This plot shows that there is a noticable fluctuation of expression levels +# over the course of the first 60 minutes of the experiment. This may be due to +# these genes being selected as having cyclically varying expression profiles. + + +# == 03.4 hist() =========================================================== +# Histograms are superbly informative and one of the workhorses of our analyses. A histogram answers the question how many observations do we have in a particular range of a (continuously varying) variable. + +# Here we plot a histogram of the first expression peaks of genes, computed as +# the first peak of the fitted model for every gene in our set. We might be +# interested in whether the expression of different genes peaks continuously +# over the cycle, or whetehr there are time intervals where waves of expression +# of different genes can be observed in response to cycle-control signals. +hist(SC$mdl$peaks) + +# We can explicitly set breakpoints for the histogram: +# Here we set breaks at 4 minute intervals +( myBreaks <- seq(0, 80, by = 4) ) # Note: 21 values bound 20 intervals +hist(SC$mdl$peaks, breaks = myBreaks) + +# Assigning the output of hist() makes the values +# used in constructing the histogram accessible: + +( H <- hist(SC$mdl$peaks) ) + +# for example, we can use this to plot the actual numbers: +text(H$mids, # midpoints of the bars + H$counts, # counts -> probabilities + labels = H$counts, # add labels to each bar: count numbers + adj = c(0.5, -0.5), # text is centered on x and raised in y + cex = 0.6, + col = "#8888FF") + +# histograms take the usual plotting parameters +myPal <- colorRampPalette(c("#00FF0055", "#0066FF55", # a "palette" function + "#00FFFF55", "#CCCCAA55", + "#FF00FF55", "#FF006655", + "#FFFFFF55"), alpha = TRUE) # transparent colors + +myBreaks <- seq(0, 80, length.out = 81) # one-minute intervals +hist(SC$mdl$peaks, # gene expression first peaks + breaks = myBreaks, + main = "Timing of first peaks of during the cell-cycle", + sub = "The bimodal distribution distinguishes replication from division", + cex.sub = 0.8, + xlab = "t (min.)", + ylab = "counts", + col = myPal(81)) # get 81 color values from myPal() + + +# === 03.4.1 overlaying histograms + +# Histograms can be plotted one-over another to compare them +# Example: when do genes with different GO term annotations first peak? + +# GO:0005730 "nucleolus" +# GO:0032200 "telomere organization" +# GO:0006260 "DNA replication" +sel1 <- grep("GO:0005730", SC$ann$GO) # 103 genes +sel2 <- grep("GO:0032200", SC$ann$GO) # 32 genes +sel3 <- grep("GO:0006260", SC$ann$GO) # 67 genes + +# define breaks. This is important to make the histograms comparable +myBreaks <- seq(0, 80, by = 4) +# plot the first histogram + +hist(SC$mdl$peaks[sel1], breaks = myBreaks, + main = "Peak timing for different GO terms", + xlab = "t (min)", + ylab = "Counts", + ylim = c(0, 50), + col = "#FF007955") + +# first overlay +hist(SC$mdl$peaks[sel2], breaks = myBreaks, + col = "#00B2FF55", + add = TRUE) + +# second overlay +hist(SC$mdl$peaks[sel3], breaks = myBreaks, + col = "#7FDFC955", + add = TRUE) + +# Legend +legend("topright", + fill = c("#FF007955", "#00B2FF55", "#7FDFC955"), + legend = c("GO:0005730\n(nucleolus)\n", # "\n" is a line-break + "GO:0032200\n(telomere organization)\n", + "GO:0006260\n(DNA replication)\n"), + cex = 0.6, + bty = "n") # no box around the legend + + +# = 04 THE plot() FUNCTION ================================================ +# plot() is the workhorse of data visualization in R. But plot() is also a "generic" - i.e. different "classes" can define their own plot-methods and plot will "dispatch" the right method for a class. + + +# == 04.1 line plots ======================================================= + +# Lines are useful to visually associate ralated data points, like points in a +# sequence of events. + +# plot an expression profile: value over time +plot(SC$xpr[169, ]) + +# note the x is implied and plotted as "index", the expression values are used +# for the y-axis by default. + +# plot types +plot(SC$xpr[169, ], type = "p") # points, the default +plot(SC$xpr[169, ], type = "l") # lines +plot(SC$xpr[169, ], type = "b") # both points and lines +plot(SC$xpr[169, ], type = "c") # empty points and lines +plot(SC$xpr[169, ], type = "o") # overplotted +plot(SC$xpr[169, ], type = "s") # steps +plot(SC$xpr[169, ], type = "h") # like histogram +plot(SC$xpr[169, ], type = "n") # none - useful to create an empty frame + # to add other elements later +text(SC$xpr[169, ], colnames(SC$xpr), cex = 0.5) # ... such as labels + +# title and axis labels +iRow <- 169 +t <- seq(0, 120, by = 5) # set actual x-values +plot(t, SC$xpr[iRow, ], + type = "b", + main = "Expression profile", + sub = "Data: GSE3653 (Pramila et al. 2002)", + xlab = "time(min)", + ylab = "Expression (log ratio)") + +# adjusting font sizes +plot(t, SC$xpr[iRow, ], + type = "b", + main = "Expression profile", + sub = "Data: GSE3653 (Pramila et al. 2002)", + xlab = "time(min)", + ylab = "Expression (log ratio)", + cex.main = 1.1, # title + cex.sub = 0.6, # subtitle + cex.axis = 0.7, # axis values + cex.lab = 0.8) # axis labels + +# x and y axis ranges can be defined with a two-element vector +plot(t, SC$xpr[iRow, ], + type = "b", + xlim = c(0, 120), # tight against the values + ylim = c(-max(abs(SC$xpr[iRow, ])), + max(abs(SC$xpr[iRow, ]))), # symmetric around 0 + main = "", sub = "", xlab = "", ylab = "") +abline(h = 0, col = "#FF000044") + +# pull in annotations with sprintf() +plot(t, SC$xpr[iRow, ], + type = "b", + main = sprintf("Expression profile: %s (%s)", + SC$ann$stdName[iRow], + SC$ann$sysName[iRow]), + sub = "", xlab = "", ylab = "") + +# When exploring data, it is useful to have a standard view of data as a +# function: + +xprPlot <- function(iRow) { + t <- seq(0, 120, by=5) + yMax <- max(abs(SC$xpr[iRow, ])) * 1.1 + + plot(t, SC$xpr[iRow, ], + ylim = c(-yMax, yMax), + type = "b", + main = sprintf("Expression profile: %s (%s)", + SC$ann$stdName[iRow], + SC$ann$sysName[iRow]), + xlab = "time(min)", + ylab = "Expression (log ratio)", + cex.main = 1.1, + cex.axis = 0.7, + cex.lab = 0.8) + abline(h = 0, col = "#FF000044") +} + +xprPlot(169) +xprPlot(1124) + +# better to visualize by plotting one OVER the other. +# This is done with the function points() + +xprPlot(1124) +points(t, SC$xpr[169, ], type = "b", cex = 0.7, col = "#EE5500CC") + + + +# In general, when we plot(), we produce a "scatterplot" of X and y values. +# scatterplot: one against the other +plot(SC$xpr[169, ], SC$xpr[1124, ]) # these two profiles are anticorrelated + + +# = 05 ENCODING INFORMATION: SYMBOL, SIZE, COLOR ========================== + +# We have many options to encode information in scatter plots (and others), in order to be able to visualize and discover patterns and relationships. The most important ones are the type of symbol to use, its size, and its color. + + + +# == 05.1 pch ("plotting character" symbols) =============================== +# There are many types of symbols available to plot points and they can all be varied by type, size, and color to present additional information. Which one to use is defined in the argument pch . The default is pch = 1: the open circle. Characters 1:20 are regular symbols: + +# reduce margins +oPar <- par(mar = c(1, 1, 1, 1)) + +# Empty plot frame ... +plot(c(0,11), c(0,11), type = "n", axes = FALSE, xlab = "", ylab = "") + +# coordinates for first 25 symbols +x1 <- c(1:10, 1:10) +y1 <- c(rep(10, 10),rep(9, 10)) +myPch <- 1:20 + +points(x1, y1, pch = myPch, cex = 1.2) + +# pch 21:25 can have different border and fill colours. Borders are defined with +# "col", fills are defined with "fill": + +x2 <- 1:5 +y2 <- rep(8,5) + +myCols <- c("#4B0055", "#00588B", "#009B95", "#53CC67", "#FDE333") + +points(x2, y2, pch=21:25, col="#708090", bg = myCols, cex = 1.8) + +# ten more symbols are defined as characters +x3 <- 1:10 +y3 <- rep(6,10) +myPch <- c(".", "o", "O", "0","a","A", "*", "+","-","|") +points(x3, y3, pch = myPch) # note: "myPCh" is a character vector while + # the other pch were identified by integers + +# The ASCII codes for characters 32 to 126 can also be used as plotting symbols +myPch <- 32:126 +x4 <- (((myPch - 32) %% 19) + 2) / 2 +y4 <- 5 - ((myPch - 32) %/% 19) +points(x4, y4, pch=32:126, col="#0055AA") + +# I don't think I have ever actually used characters as plotting characters in +# this way; it is much more straightforward to just use the text() function in +# that case, rather than having to look up which ASCII code is which character, +# and remembering which ones are available as numbers, and which ones are +# identified by the character itself. But the first twenty five I use a lot, and +# I also end up looking up their codes a lot. So here is a function to plot them for reference. You can paste it into your .Rprofile to keep it at hand: + +pchRef <- function() { + # a reference plot for the first twenty five plotting characters + + oPar <- par(mar = c(1, 1, 1, 1), mfrow = c(1, 1)) + plot(c(0,6), c(0,6), type = "n", axes = FALSE, xlab = "", ylab = "") + + x <- ((0:24) %% 5) + 1 + y <- rep(5:1, each = 5) + + idx <- 1:20 + points(x[idx], y[idx], pch = idx, cex = 2) + + idx <- 21:25 + points(x[idx], y[idx], pch = idx, cex = 2, col = "#708090", + bg=c("#CC0033AA", "#991566AA", "#662A99AA", "#323FCCAA", "#0055FFAA")) + + text(x - 0.3, y, labels = sprintf("%d:", 1:25), col = "#7766AA", cex = 0.8) + + par(oPar) +} + + +# reset margins +par(oPar) + + +# In addition to the default fonts, a large set of Hershey vector fonts is +# available which gives access to many more plotting and labeling options via +# text() +demo(Hershey) + +# Plotting other symbols: +# In the most general way, Unicode characters can be plotted as text. +# The code is passed in hexadecimal, long integer, with a negative sign. +# Here is a quarter note (Unicode: 266a) using plot() +plot(0.5,0.5, pch=-0x266aL, cex=5, xlab="", ylab="") + +# However, rendering may vary across platforms since it depends on unicode +# support. If your character can't be rendered, you'll only get an empty box. + + +# 1.1.1 Using pch to emphasize different categories + +# In general, when we plot(), we produce a "scatterplot" of X and y values. +# scatterplot: one against the other + +tRep <- c("t.15", "t.20", "t.25", "t.30") # Replication-phase time points +tDup <- c("t.40", "t.45", "t.50", "t.55") # Duplication-phase time points + +plot(rowMeans(SC$xpr[ , tRep]), rowMeans(SC$xpr[ , tDup])) + +# select top ten replication enriched GO terms +sel <- order(SC$GO$xsReplicate, decreasing = TRUE)[1:10] +SC$GO$label[sel] # what are these? +GOrep <- SC$GO$GOid[sel] # GO terms that are enriched in the Replication phase + +sel <- order(SC$GO$xsDuplicate, decreasing = TRUE)[1:10] +SC$GO$label[sel] # what are these? +GOdup <- SC$GO$GOid[sel] # GO terms that are enriched in the Duplication phase + +# define sets of genes that are annotated to one or the other GO id set +repGenes <- logical(nrow(SC$ann)) # prepare an empty vector +for (id in GOrep) { # whenever we find one of the + repGenes <- repGenes | grepl(id, SC$ann$GO) # characteristic GO IDs annotated +} # to a a gene, we put TRUE in its +sum(repGenes) # spot of the repGenes vector. +xRep <- rowMeans(SC$xpr[ repGenes, tRep]) # repGenes in the replication phase +yRep <- rowMeans(SC$xpr[ repGenes, tDup]) # repGenes in the duplication phase + +# same thing for the genes that are enriched in the duplication phase +dupGenes <- logical(nrow(SC$ann)) +for (id in GOdup) { + dupGenes <- dupGenes | grepl(id, SC$ann$GO) +} +sum(dupGenes) +xDup <- rowMeans(SC$xpr[ dupGenes, tRep]) # dupGenes in the replication phase +yDup <- rowMeans(SC$xpr[ dupGenes, tDup]) # dupGenes in the duplication phase + +# Ready to plot: +# base plot without the ref... and dup... genes +basePlot <- function(...) { + sel <- ! (repGenes | dupGenes) + m <- sprintf("Expression changes between phases (%d genes)", + nrow(SC$xpr)) + plot(rowMeans(SC$xpr[ sel, tRep]), + rowMeans(SC$xpr[ sel, tDup]), + main = m, + xlab = "Expression in Replication phase", + ylab = "Expression in Duplication phase", + cex.main = 0.9, + cex.lab = 0.8, + ...) + abline(h = 0, col = "#0088FF44") # horizontal zero + abline(v = 0, col = "#0088FF44") # vertical zero +} + +basePlot() +# plotting the rep.. and dup... sets with different pch +points(xRep, yRep, pch=24, col="#000000", bg="#FFFFFF") # white up triangle +points(xDup, yDup, pch=25, col="#000000", bg="#FFFFFF") # white down triangle + +# These are clearly different distributions, but the plot is overall too busy. + +# Using color improves the plot: +basePlot(pch=21, col="#AAAAAA", bg="#DDDDDD") +points(xRep, yRep, pch=24, col="#000000", bg="#66DDFF") +points(xDup, yDup, pch=25, col="#000000", bg="#FF77FF") + +# Even better may be to use transparent colors, to address the overlap: +basePlot(pch=19, col="#00000022") +points(xRep, yRep, pch=19, col="#33BBFF66") +points(xDup, yDup, pch=19, col="#FF44AA44") + +# this needs a legend: +legend ("bottom", + legend = c( + sprintf("%d Genes with Replication-\nenriched GO terms\n ", sum(repGenes)), + sprintf("%d Genes with Duplication-\nenriched GO terms\n ", sum(dupGenes)), + "Other Genes\n " + ), + horiz = TRUE, + pch = 19, + col = c("#33BBFF66", "#FF44AA44", "#00000022"), + cex = 0.6, + pt.cex = 1.0, + inset = 0.02, + box.col = "#DDDDDD", + bg = "#FFFFFF") + +# === 05.1.1 Line types + +# Basically all plots take arguments lty to define the line type, and lwd +# to define line width + +# empty plot ... +plot(c(0,10), c(0,10), type = "n", axes = FALSE, xlab = "", ylab = "") + +# Line type +for (i in 1:8) { + y <- 10.5-(i/2) + segments(1,y,5,y, lty=i) + text(6, y, paste("lty = ", i), col="grey60", adj=0, cex=0.75) +} + +# Line width +for (i in 1:10) { + y <- 5.5-(i/2) + segments(1,y,5,y, lwd=(0.3*i)^2) + text(6, y, paste("lwd = ", (0.3*i)^2), col="grey60", adj=0, cex=0.75) +} + +# = 06 COLOUR ============================================================= + +# Colour is immensely important for data visualization, and colours in R can be +# specified by number, by name, as hex-triplets, as rgb or hsv values, and +# through several color-space specifications. The can be used individually, as +# vectors that are defined manually, and through colour palettes. And the can be +# solid and transparent. + +# == 06.1 Colours by number ================================================ +# The col=... parameter for plots is 1 by default and you can +# set it to the range 0:8. +# 0: white +# 1: black (the default) +# 2: red +# 3: green +# 4: blue +# 5: cyan +# 6: magenta +# 7: yellow +# 8: grey +barplot(rep(1,9), col=0:8, axes=FALSE, names.arg=c(0:8)) + +# As you can see, the default primary colours are garish and offend even the +# most rudimentary sense of aesthetics. Using these colors won't earn you +# respect. Fortunately we have many more sophisticated ways to define colours. + +# == 06.2 Colours by name ================================================== +# You may have noticed that "red", "green", and "blue" work for the col=... +# parameter, but you probably would not have imagined that "peachpuff", +# "firebrick" and "goldenrod" are valid as well. In fact, there are 657 named +# colours in R. Access them all by typing: +colors() + +myCols <- c( + "firebrick2", + "tomato", + "goldenrod1", + "peachpuff", + "papayawhip", + "seashell", + "whitesmoke" +) +pie(c(1, 1, 2, 3, 5, 8, 13), + col = myCols, + labels = myCols) + +# Read more about named colours (and related topics) in Melanie Frazier's pdf: +# https://www.nceas.ucsb.edu/sites/default/files/2020-04/colorPaletteCheatsheet.pdf + +# I almost never use named colours anymore but use hex-triplets instead... + + +# == 06.3 Colours as hex-triplets ========================================== +# Hex triplets in R work exactly as in HTML: a triplet of +# RGB values in two-digit hexadecimal representation. The +# first two digits specify the red value, the second two +# are for green, then blue. R accepts a fourth pair of +# digits to optionally specify the transparency - the "Alpha" channel. The +# semantics of the hex-triplet is thus "#RRGGBB" or "#RRGGBBAA". +# Read more e.g. at http://en.wikipedia.org/wiki/Web_colors + +# The function col2rgb() converts colour names to rgb values ... +col2rgb("violetred") + +# ... and rgb() converts rgb values to hex-code: +rgb(1, 0.5, 0.23) + +# to get hexcodes from names takes a bit more: divide and transpose - +rgb(t(col2rgb("violetred")/255)) + +# Here is a utility function to convert all manners of color-vectors to +# hex-triplets. You can copy it into your .Rprofile if you want to keep it at hand. + +col2hex <- function(colV, dput = FALSE, pal = FALSE, N = NULL) { + # colV a vector or scalar of color names, hex-codes or integers + # dput logical. If TRUE, character output is suitable for pasting into code. + # pal logical. If TRUE, the colors will be used in a palette. N must + # be defined in that case + # value: a vector of hex-codes of the same length, or a dput() string + + h <- rgb(t(col2rgb(colV) / 255)) + if (pal == TRUE) { + if (is.null(N)) { stop("N must be defined if pal is TRUE.") } + h <- colorRampPalette(h)(N) + } + if (dput == TRUE) { + dput(h) + return(invisible(h)) + } + return(h) +} + +# Examples: +myCols <- c( "firebrick2", "tomato", "goldenrod1", "peachpuff", + "papayawhip", "seashell", "whitesmoke") + +col2hex(myCols) +col2hex(myCols, dput = TRUE) + +# barplot with the original colors +barplot(rep(1, length(myCols)), col=myCols) + +# expanding the palette with col2hex() +N <- 40 +barplot(rep(1, N), col = col2hex(myCols,pal=TRUE,N=N) ) + + +# === 06.3.1 Inbuilt palettes + +# In R, a palette is a function (!) that takes a number as its argument and +# returns that number of colors, those colors can then be used to color points +# in a plot or other elements. In principle, such colors can serve three +# distinct puposes: + +# Qualitative colors have a different color for different types of entities. +# Here the color serves simply to distinguish the types, it represents +# "categorical information". For example, we can assign different colors to each +# individual amino acid. + +# Sequential colors distinguish order in a sequence: the colors distinguish the first from the last element in the order. For example we often color the N-terminus of a protein blue(ish) and the C-terminus red(dish). + +# Diverging colors encode some property and are scaled to represent particular +# values of the property. For example we might represent temperature with a +# black-body radiation color palette. Note that "sequential" can be seen as +# "diverging on order". + +# R has are a number of inbuilt palettes; Here is a convenience function to view +# palettes. Input can be either a palette, the name of a hcl.colors palette, or a vector of colors: +plotPal <- function(pal, N = 40) { + if (is.character(pal)) { + if (length(pal) == 1) { # name of a hcl palette + myCols <- hcl.colors(N, pal) + m <- pal + } else { # already a color vector + myCols <- colorRampPalette(pal)(N) + m <- sprintf("Custom palette (spread to %d values)", N) + } + } else { # palette function + myCols <- pal(N) + m <- as.character(substitute(pal)) + } + barplot(rep(1,N), main = m, axes = FALSE, col = myCols) +} + +plotPal(rainbow) # rainbow() is the quintessential qualitative palette +plotPal(cm.colors) # cm.colors() is sequential: cyan-white-magenta +plotPal(terrain.colors) # a diverging palette: map elevation colors + +# A lot of thought has gone into the construction of palettes in the +# colorspace:: package, now available as hcl.colors in R (hcl: hue, chroma, +# luminance), which is a better way to specify perceptually equidistant colors. +# It's worthwhile to read through the help-file for more information - +?hcl.colors +# ... and to sample the 110 available palettes: +hcl.pals() + +# Examples: +plotPal("Viridis") +plotPal("Pastel 1") +plotPal("Spectral") +plotPal("OrRd") +plotPal("Cold") +plotPal("Mint") + +# If you are curious, you can execute the code below, then select the line that +# draws the plot and press ctrl+enter repeatedly to step through the entire +# set of palettes. + +# i <- 0 +# i<-i+1; plotPal(hcl.pals()[i]) + +# Worthy of special mention are the color-Brewer palettes, in particular for +# their accessibility considerations. Do consider that not all of us view colors +# in the same way! +if (! requireNamespace("RColorBrewer", quietly = TRUE)) { + install.packages("RColorBrewer") +} +?RColorBrewer +RColorBrewer::display.brewer.all(colorblindFriendly = TRUE) + +plotPal(RColorBrewer::brewer.pal(11, "PuOr"), N = 11) + + +# === 06.3.2 Custom palettes +# +# Bespoke palettes are easily created with colorRampPalette(). The function +# returns a palette, i.e. a function (not a vector of colors). You assign the +# function to a variable (your palette), and you call it with an argument of how +# many color values you want it to return. + + +myPal <- colorRampPalette(c("#000000", "#FFFFFF", "#FF0000")) +myPal(10) +plotPal(myPal) + +# The parameter "bias" controls the spacing of colors at the end of the palette: +plotPal(colorRampPalette(c("#000000", "#FFFFFF", "#FF0000"), bias = 0.5)) +plotPal(colorRampPalette(c("#000000", "#FFFFFF", "#FF0000"), bias = 1.0)) +plotPal(colorRampPalette(c("#000000", "#FFFFFF", "#FF0000"), bias = 2.0)) + +# But a similar effect can be obtained by duplicating one or more colours to +# spread the range ... +plotPal(colorRampPalette(c("#000000", "#FFFFFF", "#FF0000", "#FF0000"))) + +# ... and this can also be used to spread the middle +plotPal(colorRampPalette(c("#000000", "#FFFFFF", "#FFFFFF", "#FF0000"))) + +# nicer pastels +myPastels <- colorRampPalette(c("#FFFFFF", + "#FF9AA2", + "#FFB7B2", + "#FFDAC1", + "#E2F0CB", + "#B5EAD7", + "#C7CEEA", + "#FFFFFF")) +plotPal(myPastels) + +# outliers +myOutliers <- colorRampPalette(c("#66666A", + "#9999A6", + "#CCCCCA", + "#EEEEF9", + "#FFBBA8", + "#F30000"), bias = 0.5) +plotPal(myOutliers) + +# Other many sources on the Web that help to generate matching colors upon which +# to build palette. One such site with a very large selection of +# community-contributed palettes is: https://color.adobe.com/ ... and there is +# also an option to extract palettes from user-supplied images. + + +# === 06.3.3 Transparency: The Alpha Channel + +# R colours are actually specified as quartets: the fourth value +# the "Alpha channel" defines the transparency. Setting this to +# values other than "FF" (the default) can be useful for very +# crowded plots, or for creating overlays. + + +x <- SC$xpr[, "t.10"] # towards the begiining of the cycle +y <- SC$xpr[, "t.50"] # towards the end of the cycle + +# compare: +plot(x,y, pch = 19, col = "#EE3A8C") +plot(x,y, pch = 19, col = "#EE3A8C12") # Alpha at ~ 10% + +# or with multiple overlays of varying size using points() ... +plot( x,y, pch = 16, cex = 1, col = "#AA330009") +points(x,y, pch = 19, cex = 2, col = "#44558803") +points(x,y, pch = 20, cex = 0.5, col = "#EE3A8C08") + +# A similar behaviour can be obtained from "density adapted colors with the +# densCols() function +plot (x, y, col = densCols(x, y), pch = 19, cex = 1.5) + + +# ... or with the smootScatter function +smoothScatter(x, y) + +smoothScatter(x, y, + colramp = colorRampPalette(c("#66666A", + "#9999A6", + "#CCCCCA", + "#EEEEF9", + "#FFBBA8", + "#F30000"), bias = 0.5), + nrpoints = 0, + xlim = c(-0.4, 0.3), + ylim = c(-0.4, 0.3), + xlab = "Expression (t = 10 min.)", + ylab = "Expression (t = 50 min.)") +abline(h = 0, col = "#FFFFFF", lwd = 0.5) +abline(v = 0, col = "#FFFFFF", lwd = 0.5) + +# ToDo: expand to "real" density plots (cf. ) + + +# == 06.4 abline(), lines() and segments() ================================ + +# lines() draws an arbitrary line from the supplied points + +N <- 300 +x <- seq(0.8 * pi, 40 * pi, length.out = N) +y <- sin(x)/x +plot(x, y, type = "n") # only set up the axes +abline(h = 0) +lines(x, y, lwd = 4, col = "#FFFFFF") # "erase" parts of the axis +lines(x, y, col = "#CC0000") # draw the line we want + + +# Varying the color of a plotted line can't be done in base r. Use +# ggplot2:: or plotrix::color.scale.lines() instead + +if (! requireNamespace("plotrix", quietly = TRUE)) { + install.packages("plotrix") +} + +# plotrix::color.scale.lines() can change color as well as width of a line +# to give you more ways to visually display features of your data + +N <- 300 +x <- seq(0.8 * pi, 40 * pi, length.out = N) +y <- sin(x)/x +myCol <- myPastels(N) # colors +myLwd <- seq(0.5, 10, length.out = N) # line width + +plot(x, y, type = "n") # only set up the axes +abline(h = 0, lwd = 2, col = "#CCCCCC") + +plotrix::color.scale.lines(x, y, col = myCol, lwd = myLwd) + +# = 07 AXES =============================================================== + +# For Details, see: +?plot.default + +n <- 1000 +x <- rnorm(n) +y <- x^3 * 0.25 + rnorm(n, sd=0.75) + +plot(x,y) # Default + +# Axes +plot(x,y, xlim=c(-4, 4)) # fixed limits +plot(x,y, xlim=c(-4, 4), ylim=c(10, -10)) # reverse is possible +plot(x,y, log="xy") # log axes + +# The axis parameters in the default plot are limited. +# If you want more control, suppress the printing of an axis +# in the plot and use the axis() function instead. +?axis + + +# Axis-labels and title are straightforward parameters of plot +plot(x,y, xlab="rnorm(n)", + ylab="x^3 * 0.25 + rnorm(n, sd=0.75)", + cex.main=1.3, + main="Sample\nPlot", + cex.sub=0.75, + col.sub="grey", + sub="Scatterplot of noisy 3d-degree polynomial" + ) + +# Add gridlines +?grid +grid() + + +# = 08 LEGENDS ============================================================ + + +# == 08.1 basic legends ==================================================== +# +# ToDo: basic legend syntax + +# == 08.2 Color bars ======================================================= + +# Legends for a plot with a sequential or divergent spectrum are a special case, since we need to map a continuum of colors to a scale + +# Example: plotting expression levels in two cell-cycle phases against each other, and applying a divergent color spectrum that displays the model correlations: genes with low correlations have expression profiles that can not be well approximated with a periodic function. + + +tRep <- c("t.15", "t.20", "t.25", "t.30") # Replication-phase time points +tDup <- c("t.40", "t.45", "t.50", "t.55") # Duplication-phase time points +x <- rowMeans(SC$xpr[ , tRep]) +y <- rowMeans(SC$xpr[ , tDup]) +plot(x, y) + +myCors <- SC$mdl$cor +hist(myCors, breaks = 40) + +# The values are of course continuous, but we need to map them to a discrete set of colors. The strategy is as follows: +# - scale the values we wish to map into the interval [0, 1] +# - multiply with the desired number of intervals minus one, round, and add one +# - these integers can be used as indices into a vector of colors, ... +# - ... which gives the colors for the plot + +# A divergent palette +corPal <- colorRampPalette(c("#90005DAA", "#D44F99AA", "#F4C2B8AA", + "#F8DEB9AA", "#EEFAE9AA", "#D7F1C8AA", + "#BBE9C4AA", "#7CB8A6AA"), alpha = TRUE) +hist(myCors, + breaks = seq(min(myCors), max(myCors), length.out = 21), + col = corPal(20)) + +N <- 10 # We'll map into N intervals + +# The usual way to scale a vector x to the [0,1] interval +# is: x-min / max-min +idx <- (myCors - min(myCors)) / (max(myCors) - min(myCors)) +# Now: expand this to N-1, round, and add 1 +idx <- round(idx * (N-1)) + 1 +# confirm that these numbers are now suitable as indices into an 1:N vector +table(idx) +# create the color vector +( corCols <- corPal(N) ) +# and make a vector of colors, one for each gene +myCols <- corCols[idx] + +# then we plot ... +plot(x, y, pch = 16, col = myCols, cex = 1.2) + +# there is a lot of overlap - let's re-plot in order of decreasing correlations +# so the low correlations (reds) get plotted last. Ordering this way will give a +# "cleaner" appearance for divergent plots. +ord <- order(myCors, decreasing = TRUE) +plot(x[ord], y[ord], pch = 16, col = myCols[ord], cex = 1.2) + +#: Titles etc. +basePlot <- function() { + m <- sprintf("Expression changes between phases (%d genes)", + length(x)) + plot(x[ord], y[ord], + main = m, + xlab = "mean Expression in Replication phase", + ylab = "mean Expression in Duplication phase", + sub = "Colour by Correlation to Fitted Periodic Function", + cex.main = 0.9, + cex.lab = 0.7, + cex.sub = 0.7, + pch = 16, + col = myCols[ord], + cex = 1.2) + abline(h = 0, col = "#0088FF44") # horizontal zero + abline(v = 0, col = "#0088FF44") # vertical zero +} + +basePlot() + +# to print a color-bar legend, we use the plotrix package + +if (! requireNamespace("plotrix", quietly = TRUE)) { + install.packages("plotrix") +} + +corLabels <- sprintf("%4.2f", # craft the labels to plot + seq(min(myCors), + max(myCors), + length.out = length(corCols))) + +oPar <- par(mar=c(5,4,4,7)) # more space on the margin +basePlot() + +# we need to figure out where to put the legend. par("usr") gives us the coordinates of the last plot window. We'll space the legend between 1.05 and 1.1 times the plot width, and centred along the y-axis with a height of 0.8 times the plot height. +dx <- par("usr")[2] - par("usr")[1] +dy <- par("usr")[4] - par("usr")[3] +xl <- par("usr")[2] + (dx * 0.05) # left +xr <- xl + (0.05 * dx) # right +yb <- par("usr")[4] - (0.9 * dy) # bottom +yt <- yb + (0.8 * dy) # top + + +plotrix::color.legend(xl, yb, xr, yt, + corLabels, + corCols, + align = "rb", # labels to the right of the bar + cex = 0.8, + gradient = "y") # vertical gradient + +par(oPar) + + +# = 09 LAYOUT ============================================================= + +# par, lattice, constant aspect ratio + + +# Most parameters of the plot window can be set via +# the functions plot(), hist() etc., but some need to +# be set via the par() function. Calling par() without +# arguments lists the current state of the plotting +# parameters. Calling it with arguments, returns the +# old parameters and sets new parameters. Thus setting +# new parameters and saving the old ones can be done +# in one step. The parameters that have to be set via +# par include: + +# - multiple plots in one window (mfrow, mfcol, mfg) +# - margin layout (mai, mar mex, oma, omd, omi) +# - controlling position and size of a plot in the figure (fig, plt, ps, pty) +# - see ?par for details. + +n <- 1000 +x <- rnorm(n) +y <- x^3 * 0.25 + rnorm(n, sd=0.75) + +# set window background and plotting axes via par +opar <- par(bg="steelblue", fg="lightyellow") +# set axis lables and titles via plot parameters +plot(x,y, col.axis="lightyellow", col.lab="lightyellow") +par(opar) # reset to old values + +plot(x,y) # confirm reset + +# aspect! ... +# + +# = 10 TEXT =============================================================== + + + +# Plotting arbitrary text +# use the text() function to plot characters and strings to coordinates +?text + +# Example: add labels to the symbols +# first set: plain symbols (1 to 20) +text(x1-0.4, y1, paste(1:20), cex=0.75) +# symbols with separate background (21 to 25) +text(x2-0.4, y2, paste(21:25), cex=0.75) +# third set: special characters, change font for clarity +text(x3-0.4, y3, extra, col="slateblue", cex=0.75, vfont=c("serif", "plain")) + + +# ToDo: writing into the margin ... + + + +# = 11 DRAWING ON PLOTS =================================================== + + +# To plot lines on top of plots there are several options: + +# segments() # for lines with known endpoints +# lines() # joining x,y coordinate lists with lines +# arrows() # ... but to get a filled arrow use polygon() +# curve() # drawing a curve based on a given expression +# abline() # drawing horizontal or vertical lines or lines with +# # given slope and intercept () + + +# Example: dividing a plot into 60° regions, centred on a point. +# A general approach to "lines" on a plot is provided by segments(). +# However in this special case one can use abline(). +# We have to take care though that the aspect ratio for the +# plot is exactly 1 - otherwise our angles are not right. +# Therefore we need to set the asp parameter for plots. + +# For a general sketch +# - we plot the frame a bit larger, don't draw axes +# - draw the ablines +# - draw two arrows to symbolize the coordinate axes + +p <- c(4, 2) +plot(p[1], p[2], + xlim=c(-0.5,10.5), + ylim=c(-0.5,10.5), + xlab="", ylab="", + axes=FALSE, + asp=1.0) +abline(h=p[2], lty=2) # horizontal +abline(p[2] - (p[1]*tan(pi/3)), tan(pi/3), lty=2) # intercept, slope +abline(p[2] + (p[1]*tan(pi/3)), -tan(pi/3), lty=2) # intercept, slope +arrows(0, 0, 10, 0, length=0.1) # length of arrow +arrows(0, 0, 0, 10, length=0.1) + + +# curves() +# rect() +# polygon() + +# example: plot the area under the normal distribution as a polygon, and overlay +# a histogram +set.seed(12357) +x <- rnorm(50) +sig <- 1.0 +myBreaks <- seq(-3 * sig, 3 * sig, by = 0.5 * sig) + +# first, draw a histogram but don't show the bars +hist(x, breaks = myBreaks, freq = FALSE, col = "#FFFFFF", border = "#FFFFFF") + +# next, plot the curve as a polygon() into the existing plot area +dn <- seq(-3, 3, length.out = 100) +polygon(dn, dnorm(dn), col = "#00FF8822", border = "#FFFFFF") + +# finally, replot the histogram. Setting "add = TRUE" plots it over the +# previous histogram +hist(x, breaks = myBreaks, freq = FALSE, + col = "#0000FF22", border = "#00000033", # note: transparent colours + add = TRUE) + +# +# More: see the Index of functions for the graphics package + + +# = 12 IMAGES ============================================================= + +# Todo + + +# = 13 CONTOUR LINES ====================================================== + +# ToDo + + +# = 14 3D PLOTS =========================================================== + +# ToDo + + +# = 15 GRAPHS AND NETWORKS ================================================ + +# ToDo + + +# = 16 OTHER GRPAHICS PACKAGES ============================================ + +# Packages in the standard distribution ... +# +# graphics:: +# grid:: +# lattice:: + +# Packages that can be downloaded from CRAN +# ... use with install.packages("package"), then +# library("package") + +# hexbin:: +# ggplot2:: + +# Packages that can be downloaded from BioConductor +# prada: +# if (! requireNamespace("prada", quietly=TRUE)) { +# if (! requireNamespace("BiocManager", quietly=TRUE)) { +# install.packages("BiocManager") +# } +# BiocManager::install("prada") +# } + +# = 17 INTERACTIVE PLOTS ================================================== + +# ToDo + +# == 17.1 locator() ======================================================== +# ToDo + +# == 17.2 plotly:: ========================================================= +# ToDo + + + + +# [End] +