• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

NRCan / PlotFTIR / 29881716206

22 Jul 2026 12:55AM UTC coverage: 97.203% (-1.0%) from 98.238%
29881716206

Pull #36

github

web-flow
Merge 7b3b2ed32 into 8831c26fc
Pull Request #36: Add Peak Detection and Deconvolution

1237 of 1288 new or added lines in 6 files covered. (96.04%)

2676 of 2753 relevant lines covered (97.2%)

2716.37 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.35
/R/plot_ftir.R
1
# Plot FTIR Spectra
2

3
#' PlotFTIR core plot generator
4
#'
5
#' @description Plot the FTIR spectra in a journal prepared format. Call
6
#'   [plot_ftir()] for basic (overlaid) plots and [plot_ftir_stacked()] for
7
#'   stacked and offset plots.
8
#'
9
#'   Tracez les spectres IRTF dans un format préparé par un journal. Appelez
10
#'   [plot_ftir()] pour les tracés de base (superposés) et [plot_ftir_stacked()]
11
#'   pour les tracés empilés et décalés.
12
#'
13
#' @inheritParams .shared-params
14
#'
15
#' @param ftir A data.frame in long format with columns `sample_id`,
16
#'   `wavenumber`, and `absorbance`. The `absorbance` column may be replaced by
17
#'   a `transmittance` column for transmittance plots. The code determines the
18
#'   correct y axis units and labels the plot/adjusts the margins appropriately.
19
#'
20
#'   Un data.frame au format long avec les colonnes `sample_id`, `wavenumber`,
21
#'   et `absorbance`. La colonne `absorbance` peut être remplacée par une
22
#'   colonne `transmittance` pour les tracés de transmission. Le code détermine
23
#'   les unités correctes de l'axe y et étiquette le tracé/ajuste les marges de
24
#'   manière appropriée.
25
#'
26
#' @keywords internal
27
#'
28
#' @return a ggplot object containing a  FTIR spectral plot. The plot and legend
29
#'   titles are as provided, with each sample provided a different default
30
#'   color. Because this is a ggplot object, any other ggplot modifiers, layers,
31
#'   or changes can be applied to the returned object. Further manipulations can
32
#'   be performed by this package. Peut également fournir `en`, `english` ou
33
#'   `anglais`.
34
#'
35
#'   un objet ggplot contenant un tracé spectral IRTF. Les titres de le tracé et
36
#'   de la légende sont tels que fournis, avec une couleur par défaut différente
37
#'   pour chaque échantillon. Puisqu'il s'agit d'un objet ggplot, tous les
38
#'   autres modificateurs, calques ou changements ggplot peuvent être appliqués
39
#'   à l'objet retourné. D'autres manipulations peuvent être effectuées par ce
40
#'   package.
41
#'
42
#' @seealso [zoom_in_on_range()] to 'zoom' into a specified range,
43
#'   [compress_low_energy()] to make the x axis non-linear (compressing lower
44
#'   energy regions), [add_wavenumber_marker()] to add markers to highlight
45
#'   important wavenumbers, and [move_plot_legend()] to modify the legend
46
#'   position.
47
#'
48
#'   [zoom_in_on_range()] pour 'zoomer' sur une gamme spécifiée,
49
#'   [compress_low_energy()] pour rendre l'axe x non linéaire (en compression
50
#'   les régions à basse énergie), [add_wavenumber_marker()] pour ajouter des
51
#'   marqueurs afin de mettre en évidence les nombres d'ondes importants, et
52
#'   [move_plot_legend()] pour modifier la position de la légende.
53
#'
54
plot_ftir_core <- function(
55
  ftir,
56
  plot_title = "FTIR Spectra",
57
  legend_title = "Sample ID",
58
  lang = NA
59
) {
60
  # Package Checks
61
  if (!requireNamespace("ggplot2", quietly = TRUE)) {
78✔
62
    cli::cli_abort(c(
1✔
63
      "{.pkg PlotFTIR} requires {.pkg ggplot2} package installation.",
1✔
64
      i = "Install {.pkg ggplot2} with {.run install.packages('ggplot2')}"
1✔
65
    ))
1✔
66
  }
67

68
  if (!requireNamespace("scales", quietly = TRUE)) {
77✔
NEW
69
    cli::cli_abort(c(
×
NEW
70
      "{.pkg PlotFTIR} requires {.pkg scales} package installation.",
×
NEW
71
      i = "Install {.pkg scales} with {.run install.packages('scales')}"
×
NEW
72
    ))
×
73
  }
74

75
  ftir <- check_ftir_data(ftir)
77✔
76
  if (!is.character(plot_title) || length(plot_title) > 2) {
77✔
77
    cli::cli_abort(
4✔
78
      "Error in {.fn PlotFTIR:::plot_ftir_core}. {.arg plot_title} must be a character string or vector of strings with length not more than two."
4✔
79
    )
4✔
80
  }
81
  if (!is.character(legend_title) || length(legend_title) > 1) {
73✔
82
    cli::cli_abort(
2✔
83
      "Error in {.fn PlotFTIR:::plot_ftir_core}. {.arg legend_title} must be a single character string."
2✔
84
    )
2✔
85
  }
86
  if (length(unique(ftir$sample_id)) > 12) {
71✔
87
    cli::cli_warn(c(
17✔
88
      "Warning in {.fn PlotFTIR:::plot_ftir_core}. The color palette in use works best with 12 or fewer unique samples in {.arg ftir}.",
17✔
89
      i = "You have a total of {length(unique(ftir$sample_id))} unique sample IDs."
17✔
90
    ))
17✔
91
  }
92

93
  # if language is provided, check against permitted, else use default from options
94
  if (!is.na(lang)) {
71✔
95
    lang <- rlang::arg_match(
31✔
96
      lang,
31✔
97
      values = c(
31✔
98
        "en",
31✔
99
        "english",
31✔
100
        "anglais",
31✔
101
        "fr",
31✔
102
        "french",
31✔
103
        "francais",
31✔
104
        "fran\u00e7ais"
31✔
105
      ),
31✔
106
      multiple = FALSE
31✔
107
    )
31✔
108
  } else {
109
    lang <- getOption("PlotFTIR.lang", default = "en")
40✔
110
  }
111

112
  l <- substr(lang, 0, 2)
67✔
113
  if (l == "fr") {
67✔
114
    if (all(plot_title == "FTIR Spectra")) {
8✔
115
      plot_title <- "Spectres IRTF"
2✔
116
    }
117
    if (legend_title == "Sample ID") {
8✔
118
      legend_title <- "ID de l'\u00e9chantillon"
4✔
119
    }
120
  }
121

122
  mode <- attr(ftir, "intensity")
67✔
123

124
  if (l == "fr") {
67✔
125
    xtitle <- bquote("Nombre d'onde" ~ (cm^-1))
8✔
126
  } else {
127
    xtitle <- bquote("Wavenumber" ~ (cm^-1))
59✔
128
  }
129

130
  ytitle <- ifelse(
67✔
131
    mode %in% c("absorbance", "normalized absorbance"),
67✔
132
    "Absorbance",
67✔
133
    "% Transmittance"
67✔
134
  )
67✔
135

136
  if (grepl("normalized", mode)) {
67✔
137
    ytitle <- paste("Normalized", ytitle)
2✔
138
  }
139

140
  ftir <- ftir[stats::complete.cases(ftir), ]
67✔
141
  ftir$wavenumber <- as.numeric(ftir$wavenumber)
67✔
142

143
  if (grepl("absorbance", mode)) {
67✔
144
    ftir$absorbance <- as.numeric(ftir$absorbance)
59✔
145
    p <- ggplot2::ggplot(ftir) +
59✔
146
      ggplot2::geom_line(ggplot2::aes(
59✔
147
        x = .data$wavenumber,
59✔
148
        y = .data$absorbance,
59✔
149
        color = as.factor(.data$sample_id)
59✔
150
      )) +
59✔
151
      ggplot2::scale_y_continuous()
59✔
152
  } else {
153
    ftir$transmittance <- as.numeric(ftir$transmittance)
8✔
154
    p <- ggplot2::ggplot(ftir) +
8✔
155
      ggplot2::geom_line(ggplot2::aes(
8✔
156
        x = .data$wavenumber,
8✔
157
        y = .data$transmittance,
8✔
158
        color = as.factor(.data$sample_id)
8✔
159
      )) +
8✔
160
      ggplot2::scale_y_continuous(breaks = scales::breaks_width(20)) +
8✔
161
      ggplot2::coord_cartesian(ylim = c(0, 100))
8✔
162
  }
163

164
  p <- p +
67✔
165
    ggplot2::labs(
67✔
166
      title = plot_title[1],
67✔
167
      subtitle = if (length(plot_title) < 2) NULL else plot_title[2], # Can't return Null from ifelse()
67✔
168
      x = xtitle,
67✔
169
      y = ytitle
67✔
170
    ) +
67✔
171
    ggplot2::guides(
67✔
172
      color = ggplot2::guide_legend(title = legend_title),
67✔
173
      x = ggplot2::guide_axis(minor.ticks = TRUE)
67✔
174
    ) +
67✔
175
    ggplot2::theme_light() +
67✔
176
    ggplot2::scale_x_reverse(
67✔
177
      breaks = scales::breaks_extended(),
67✔
178
      expand = ggplot2::expansion()
67✔
179
    )
67✔
180

181
  if (
182
    !requireNamespace("ggthemes", quietly = TRUE) ||
67✔
183
      length(unique(ftir$sample_id)) > 15
67✔
184
  ) {
185
    p <- p +
36✔
186
      ggplot2::scale_color_viridis_d()
36✔
187
  } else {
188
    p <- p +
31✔
189
      ggthemes::scale_color_calc()
31✔
190
  }
191

192
  if (grepl("normalized", mode)) {
67✔
193
    p <- p +
2✔
194
      ggplot2::theme(
2✔
195
        axis.text.y = ggplot2::element_blank()
2✔
196
      )
2✔
197
  }
198

199
  attr(p, "intensity") <- attr(ftir, "intensity")
67✔
200

201
  return(p)
67✔
202
}
203

204

205
#' Plot FTIR in stacked format
206
#'
207
#' @description Plot the FTIR spectra in a journal prepared format. It may be
208
#'  desirable to plot spectra 'stacked and offset' by a certain amount. In this
209
#'  case the y axis becomes non-labelled and each charts baseline (0 for
210
#'  absorbance or 100 for transmittance) is offset by a certain amount.
211
#'
212
#'  Tracez les spectres IRTF dans un format préparé par un journal. Il peut être
213
#'  souhaitable de tracer les spectres 'empilés et décalés' d'une
214
#'  certaine quantité. Dans ce cas l'axe y devient non étiqueté et
215
#'  chaque ligne de base du graphique (0 pour absorbance ou 100 pour la
216
#'  transmittance) est décalée d'une certaine quantité.
217
#'
218
#' @inheritParams plot_ftir_core
219
#' @param stack_offset The amount in percentage of stacking offset to use. For
220
#'  transmittance this is directly linked to the units of Y axis, for absorbance
221
#'  this is about 0.2 absorbance units.
222
#'
223
#'  Le montant en pourcentage de décalage d'empilement à utiliser. Pour
224
#'  transmittance, cette valeur est directement liée aux unités de l'axe y, pour
225
#'  l'absorbance cela représente environ 0,2 unités d'absorbance.
226
#'
227
#' @inherit plot_ftir_core return
228
#'
229
#' @inherit plot_ftir_core seealso
230
#' @export
231
#'
232
#' @examples
233
#' if (requireNamespace("ggplot2", quietly = TRUE)) {
234
#'   # Plot FTIR spectras stacked showing the differences in the `biodiesel` dataset
235
#'   plot_ftir_stacked(biodiesel)
236
#' }
237
plot_ftir_stacked <- function(
238
  ftir,
239
  plot_title = "FTIR Spectra",
240
  legend_title = "Sample ID",
241
  stack_offset = 10,
242
  lang = NA
243
) {
244
  ftir <- check_ftir_data(ftir)
22✔
245

246
  if (!is.numeric(stack_offset) || length(stack_offset) > 1) {
12✔
247
    cli::cli_abort(
2✔
248
      "Error in {.fn PlotFTIR:::plot_ftir_stacked}. {.arg stack_offset} must be a single numeric value."
2✔
249
    )
2✔
250
  }
251
  if (stack_offset < 0 || stack_offset > 200) {
10✔
252
    cli::cli_abort(
2✔
253
      "Error in {.fn PlotFTIR:::plot_ftir_stacked}. {.arg stack_offset} must be between 0 and 200."
2✔
254
    )
2✔
255
  }
256

257
  mode <- attr(ftir, "intensity")
8✔
258

259
  # Stack FTIR traces by 10% of range number of unique samples
260
  stack_samples <- unique(ftir$sample_id)
8✔
261
  nsamples <- length(unique(stack_samples))
8✔
262

263
  if (nsamples > 1) {
8✔
264
    if (grepl("absorbance", mode)) {
8✔
265
      # Transmittance gets an offset of stack_offset % against a percentage scale
266
      # for absorbance, most signals max out around 2 so that's the range.
267
      stack_offset <- (stack_offset / 100) * 2.0
4✔
268
    }
269
    offset <- data.frame(
8✔
270
      "sample_id" = stack_samples,
8✔
271
      "offset" = seq(from = 0, by = stack_offset, length.out = nsamples)
8✔
272
    )
8✔
273

274
    ftir <- merge(x = ftir, y = offset, by = "sample_id")
8✔
275
    if (grepl("absorbance", mode)) {
8✔
276
      ftir$absorbance <- ftir$absorbance + ftir$offset
4✔
277
    } else {
278
      ftir$transmittance <- ftir$transmittance + ftir$offset
4✔
279
    }
280
    ftir$offset <- NULL
8✔
281
  }
282

283
  p <- plot_ftir_core(
8✔
284
    ftir = ftir,
8✔
285
    plot_title = plot_title,
8✔
286
    legend_title = legend_title,
8✔
287
    lang = lang
8✔
288
  )
8✔
289

290
  p <- p + ggplot2::theme(axis.text.y = ggplot2::element_blank())
6✔
291
  suppressMessages(p <- p + ggplot2::coord_cartesian(ylim = c(0, NA)))
6✔
292

293
  if (grepl("absorbance", mode)) {
6✔
294
    p <- p + ggplot2::ylab("Absorbance (a.u.)")
2✔
295
  } else {
296
    p <- p + ggplot2::ylab("Transmittance (a.u.)")
4✔
297
  }
298

299
  attr(p, "spectra_style") <- "stacked"
6✔
300

301
  return(p)
6✔
302
}
303

304

305
#' Plot FTIR Spectra Overlaid
306
#'
307
#' @description Produce a basic spectra overlay plot for all samples found in
308
#' the FTIR dataset provided.
309
#'
310
#' Produisez un tracé de base de superposition de spectres pour tous les
311
#' échantillons trouvés dans l'ensemble de données IRTF fourni.
312
#'
313
#' @inherit plot_ftir_core params return
314
#' @export
315
#'
316
#' @examples
317
#' if (requireNamespace("ggplot2", quietly = TRUE)) {
318
#'   # Plot a basic FTIR Spectra overlay from the `sample_spectra` data set with default titles
319
#'   plot_ftir(sample_spectra)
320
#' }
321
plot_ftir <- function(
322
  ftir,
323
  plot_title = "FTIR Spectra",
324
  legend_title = "Sample ID",
325
  lang = NA
326
) {
327
  ftir <- check_ftir_data(ftir)
80✔
328
  p <- plot_ftir_core(
70✔
329
    ftir = ftir,
70✔
330
    plot_title = plot_title,
70✔
331
    legend_title = legend_title,
70✔
332
    lang = lang
70✔
333
  )
70✔
334

335
  attr(p, "spectra_style") <- "normal"
61✔
336

337
  return(p)
61✔
338
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc