1<!DOCTYPE html> 2<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"><head> 3 4<meta charset="utf-8"> 5<meta name="generator" content="quarto-1.5.57"> 6 7<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes"> 8 9 10<title>Software â MÃ¥ns Thulin</title> 11<style> 12code{white-space: pre-wrap;} 13span.smallcaps{font-variant: small-caps;} 14div.columns{display: flex; gap: min(4vw, 1.5em);} 15div.column{flex: auto; overflow-x: auto;} 16div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;} 17ul.task-list{list-style: none;} 18ul.task-list li input[type="checkbox"] { 19 width: 0.8em; 20 margin: 0 0.8em 0.2em -1em; /* quarto-specific, see https://github.com/quarto-dev/quarto-cli/issues/4556 */ 21 vertical-align: middle; 22} 23</style> 24 25
26<script src="site_libs/quarto-nav/quarto-nav.js"></script>
vendor: 1 bytes, line 26
26
27<script src="site_libs/clipboard/clipboard.min.js"></script>
vendor: 1 bytes, line 27
27
28<script src="site_libs/quarto-search/autocomplete.umd.js"></script>
vendor: 1 bytes, line 28
28
29<script src="site_libs/quarto-search/fuse.min.js"></script>
vendor: 1 bytes, line 29
29
30<script src="site_libs/quarto-search/quarto-search.js"></script>
30 31<meta name="quarto:offset" content="./">
32<script src="site_libs/quarto-html/quarto.js"></script>
vendor: 1 bytes, line 32
32
33<script src="site_libs/quarto-html/popper.min.js"></script>
vendor: 1 bytes, line 33
33
34<script src="site_libs/quarto-html/tippy.umd.min.js"></script>
34 35<link href="site_libs/quarto-html/tippy.css" rel="stylesheet"> 36<link href="site_libs/quarto-html/quarto-syntax-highlighting.css" rel="stylesheet" id="quarto-text-highlighting-styles">
37<script src="site_libs/bootstrap/bootstrap.min.js"></script>
37 38<link href="site_libs/bootstrap/bootstrap-icons.css" rel="stylesheet"> 39<link href="site_libs/bootstrap/bootstrap.min.css" rel="stylesheet" id="quarto-bootstrap" data-mode="light">
40<script src="site_libs/quarto-contrib/iconify-2.1.0/iconify-icon.min.js"></script>
vendor: 1 bytes, line 40
40
41<script id="quarto-search-options" type="application/json">{ 42 "location": "navbar", 43 "copy-button": false, 44 "collapse-after": 3, 45 "panel-placement": "end", 46 "type": "overlay", 47 "limit": 50, 48 "keyboard-shortcut": [ 49 "f", 50 "/", 51 "s" 52 ], 53 "show-item-context": false, 54 "language": { 55 "search-no-results-text": "No results", 56 "search-matching-documents-text": "matching documents", 57 "search-copy-link-title": "Copy link to search", 58 "search-hide-matches-text": "Hide additional matches", 59 "search-more-match-text": "more match in this document", 60 "search-more-matches-text": "more matches in this document", 61 "search-clear-button-title": "Clear", 62 "search-text-placeholder": "", 63 "search-detached-cancel-button-title": "Cancel", 64 "search-submit-button-title": "Submit", 65 "search-label": "Search" 66 } 67}</script>
67 68 69 70<link rel="stylesheet" href="styles.css"> 71</head> 72 73<body class="nav-fixed"> 74 75<div id="quarto-search-results"></div> 76 <header id="quarto-header" class="headroom fixed-top"> 77 <nav class="navbar navbar-expand-lg " data-bs-theme="dark"> 78 <div class="navbar-container container-fluid"> 79 <div class="navbar-brand-container mx-auto"> 80 <a class="navbar-brand" href="./index.html"> 81 <span class="navbar-title">MÃ¥ns Thulin</span> 82 </a> 83 </div> 84 <div id="quarto-search" class="" title="Search"></div> 85 <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarCollapse" aria-controls="navbarCollapse" role="menu" aria-expanded="false" aria-label="Toggle navigation" onclick="if (window.quartoToggleHeadroom) { window.quartoToggleHeadroom(); }"> 86 <span class="navbar-toggler-icon"></span> 87</button> 88 <div class="collapse navbar-collapse" id="navbarCollapse"> 89 <ul class="navbar-nav navbar-nav-scroll me-auto"> 90 <li class="nav-item"> 91 <a class="nav-link" href="./index.html"> 92<span class="menu-text">About</span></a> 93 </li> 94 <li class="nav-item"> 95 <a class="nav-link" href="./blog.html"> 96<span class="menu-text">Blog</span></a> 97 </li> 98 <li class="nav-item"> 99 <a class="nav-link active" href="./R.html" aria-current="page"> 100<span class="menu-text">Software</span></a> 101 </li> 102 <li class="nav-item"> 103 <a class="nav-link" href="./consulting.html"> 104<span class="menu-text">Consulting</span></a> 105 </li> 106 <li class="nav-item"> 107 <a class="nav-link" href="./courses.html"> 108<span class="menu-text">Courses with R</span></a> 109 </li> 110 <li class="nav-item"> 111 <a class="nav-link" href="./publications.html"> 112<span class="menu-text">Publications</span></a> 113 </li> 114</ul> 115 <ul class="navbar-nav navbar-nav-scroll ms-auto"> 116 <li class="nav-item"> 117 <a class="nav-link" href="mailto:[email protected]"> 118<span class="menu-text"><iconify-icon role="img" inline="" icon="bi:envelope" style="font-size: 1.1em;" aria-label="Icon envelope from bi Iconify.design set." title="E-mail"></iconify-icon></span></a> 119 </li> 120 <li class="nav-item"> 121 <a class="nav-link" href="https://www.linkedin.com/in/mansthulin/"> 122<span class="menu-text"><iconify-icon role="img" inline="" icon="fa6-brands:linkedin" style="font-size: 1.1em;" aria-label="Icon linkedin from fa6-brands Iconify.design set." title="LinkedIn"></iconify-icon></span></a> 123 </li> 124 <li class="nav-item"> 125 <a class="nav-link" href="https://bsky.app/profile/mansthulin.bsky.social"> 126<span class="menu-text"><iconify-icon role="img" inline="" icon="fa6-brands:bluesky" style="font-size: 1.1em;" aria-label="Icon bluesky from fa6-brands Iconify.design set." title="Bluesky"></iconify-icon></span></a> 127 </li> 128 <li class="nav-item"> 129 <a class="nav-link" href="https://github.com/mthulin"> 130<span class="menu-text"><iconify-icon role="img" inline="" icon="fa6-brands:github" style="font-size: 1.1em;" aria-label="Icon github from fa6-brands Iconify.design set." title="GitHub"></iconify-icon></span></a> 131 </li> 132 <li class="nav-item"> 133 <a class="nav-link" href="https://scholar.google.se/citations?user=IMpsFTEAAAAJ"> 134<span class="menu-text"><iconify-icon role="img" inline="" icon="fa6-brands:google-scholar" style="font-size: 1.1em;" aria-label="Icon google-scholar from fa6-brands Iconify.design set." title="Google Scholar"></iconify-icon></span></a> 135 </li> 136</ul> 137 </div> <!-- /navcollapse --> 138 <div class="quarto-navbar-tools"> 139</div> 140 </div> <!-- /container-fluid --> 141 </nav> 142</header> 143<!-- content --> 144<div id="quarto-content" class="quarto-container page-columns page-rows-contents page-layout-article page-navbar"> 145<!-- sidebar --> 146<!-- margin-sidebar --> 147 <div id="quarto-margin-sidebar" class="sidebar margin-sidebar"> 148 <nav id="TOC" role="doc-toc" class="toc-active"> 149 <h2 id="toc-title">On this page</h2> 150 <ul> 151 <li><a href="#r-packages" id="toc-r-packages" class="nav-link active" data-scroll-target="#r-packages">R packages</a></li> 152 <li><a href="#shiny-apps" id="toc-shiny-apps" class="nav-link" data-scroll-target="#shiny-apps">Shiny apps</a></li> 153 </ul> 154</nav> 155 </div> 156<!-- main --> 157<div class="quarto-about-solana column-body"> 158 <div class="about-entity"> 159 <div class="entity-contents"> 160 <header id="title-block-header" class="quarto-title-block default"> 161<div class="quarto-title"> 162<h1 class="title">Software</h1> 163</div> 164<div class="quarto-title-meta"> 165 </div> 166</header> 167 <div class="about-contents"><main class="content" id="quarto-document-content"> 168 169<p>Iâm a long-time R user, and when time permits, I try to contribute to the R community. My book <a href="http://www.modernstatisticswithr.com">Modern Statistics with R</a> is freely available online.</p> 170<section id="r-packages" class="level2"> 171<h2 data-anchor-id="r-packages">R packages</h2> 172<table class="table-striped table-hover caption-top table"> 173<colgroup> 174<col style="width: 17%"> 175<col style="width: 29%"> 176<col style="width: 34%"> 177<col style="width: 19%"> 178</colgroup> 179<thead> 180<tr class="header"> 181<th>Package</th> 182<th>Co-authors</th> 183<th>Description</th> 184<th>Links</th> 185</tr> 186</thead> 187<tbody> 188<tr class="odd"> 189<td><code>boot.pval</code></td> 190<td></td> 191<td>
191Convenience functions for computing bootstrap p-values, including for various regression models and tests of location</td> 192<td><a href="https://cran.r-project.org/package=boot.pval">CRAN</a> <br> <a href="https://github.com/mthulin/boot.pval">GitHub</a> <br> <a href="https://mthulin.github.io/boot.pval/">Documentation</a></td> 193</tr> 194<tr class="even"> 195<td><code>ivo.table</code></td> 196<td>Kajsa Grind<br>Stefan Furne</td> 197<td>Nicely formatted frequency tables and contingency tables.</td> 198<td><a href="https://cran.r-project.org/package=ivo.table">CRAN</a> <br> <a href="https://github.com/mthulin/ivo.table">GitHub</a> <br> <a href="https://mthulin.github.io/ivo.table/">Documentation</a></td> 199</tr> 200<tr class="odd"> 201<td><code>nullabor</code></td> 202<td>Di Cook<br>Hadley Wickham<br>Niladri Roy Chowdhury<br>Heike Hofmann</td> 203<td>Visual tools for assessing distributional assumptions and residuals in linear regression models.</td> 204<td><a href="https://cran.r-project.org/package=nullabor">CRAN</a> <br> <a href="https://github.com/dicook/nullabor">GitHub</a> <br> <a href="https://dicook.github.io/nullabor/">Documentation</a></td> 205</tr> 206<tr class="even"> 207<td><code>MultSurvTests</code></td> 208<td>Lukas Arnroth</td> 209<td>Multivariate version of the two-sample logrank and Gehan tests.</td> 210<td><a href="https://cran.r-project.org/package=MultSurvTests">CRAN</a> <br> <a href="https://github.com/lukketotte/MultSurvTests">GitHub</a></td> 211</tr> 212<tr class="odd"> 213<td><code>pipedreams</code></td> 214<td></td> 215<td>Pipe-friendly wrappers for common R statistical tests and models.</td> 216<td><a href="https://github.com/mthulin/pipedreams">GitHub</a></td> 217</tr> 218</tbody> 219</table> 220<!-- 221- `boot.pval`<br>Convenience functions for computing bootstrap p-values, including for various regression models and tests of location.<br /><a href="https://cran.r-project.org/package=boot.pval">CRAN</a> / <a href="https://github.com/mthulin/boot.pval">GitHub</a> / <a href="https://mthulin.github.io/boot.pval/">Documentation</a> 222- `ivo.table` *(with Kajsa Grind and Stefan Furne)*<br>Nicely formatted frequency tables and contingency tables.<br /><a href="https://cran.r-project.org/package=ivo.table">CRAN</a> / <a href="https://github.com/mthulin/ivo.table">GitHub</a> / <a href="https://mthulin.github.io/ivo.table/">Documentation</a> 223- `nullabor` *(with Di Cook, Hadley Wickham, Niladri Roy Chowdhury, and Heike Hofmann)*<br>Visual tools for assessing distributional assumptions and residuals in linear regression models.<br /><a href="https://cran.r-project.org/package=nullabor">CRAN</a> / <a href="https://github.com/dicook/nullabor">GitHub</a> / <a href="https://dicook.github.io/nullabor/">Documentation</a> 224- `MultSurvTests` *(with Lukas Arnroth)*<br>Multivariate version of the two-sample logrank and Gehan tests.<br /><a href="https://cran.r-project.org/package=MultSurvTests">CRAN</a> / <a href="https://github.com/lukketotte/MultSurvTests">GitHub</a> 225--> 226</section> 227<section id="shiny-apps" class="level2"> 228<h2 data-anchor-id="shiny-apps">Shiny apps</h2> 229<p>The <a href="https://www.mansthulin.se/bat">Bioscreen Analysis Tool</a>, developed using R and Shiny, is an online app for analysis of growth curves from Bioscreen C MBR machines. The source code is available on <a href="https://github.com/mthulin/BAT">GitHub</a>.</p> 230 231 232</section> 233</main></div> 234 </div> 235 </div> 236</div> 237 <!-- /main -->
238<script id="quarto-html-after-body" type="application/javascript"> 239window.document.addEventListener("DOMContentLoaded", function (event) { 240 const toggleBodyColorMode = (bsSheetEl) => { 241 const mode = bsSheetEl.getAttribute("data-mode"); 242 const bodyEl = window.document.querySelector("body"); 243 if (mode === "dark") { 244 bodyEl.classList.add("quarto-dark"); 245 bodyEl.classList.remove("quarto-light"); 246 } else { 247 bodyEl.classList.add("quarto-light"); 248 bodyEl.classList.remove("quarto-dark"); 249 } 250 } 251 const toggleBodyColorPrimary = () => { 252 const bsSheetEl = window.document.querySelector("link#quarto-bootstrap"); 253 if (bsSheetEl) { 254 toggleBodyColorMode(bsSheetEl); 255 } 256 } 257 toggleBodyColorPrimary(); 258 const isCodeAnnotation = (el) => { 259 for (const clz of el.classList) { 260 if (clz.startsWith('code-annotation-')) { 261 return true; 262 } 263 } 264 return false; 265 } 266 const onCopySuccess = function(e) { 267 // button target 268 const button = e.trigger; 269 // don't keep focus 270 button.blur(); 271 // flash "checked" 272 button.classList.add('code-copy-button-checked'); 273 var currentTitle = button.getAttribute("title"); 274 button.setAttribute("title", "Copied!"); 275 let tooltip; 276 if (window.bootstrap) { 277 button.setAttribute("data-bs-toggle", "tooltip"); 278 button.setAttribute("data-bs-placement", "left"); 279 button.setAttribute("data-bs-title", "Copied!"); 280 tooltip = new bootstrap.Tooltip(button, 281 { trigger: "manual", 282 customClass: "code-copy-button-tooltip", 283 offset: [0, -8]}); 284 tooltip.show(); 285 } 286 setTimeout(function() { 287 if (tooltip) { 288 tooltip.hide(); 289 button.removeAttribute("data-bs-title"); 290 button.removeAttribute("data-bs-toggle"); 291 button.removeAttribute("data-bs-placement"); 292 } 293 button.setAttribute("title", currentTitle); 294 button.classList.remove('code-copy-button-checked'); 295 }, 1000); 296 // clear code selection 297 e.clearSelection(); 298 } 299 const getTextToCopy = function(trigger) { 300 const codeEl = trigger.previousElementSibling.cloneNode(true); 301 for (const childEl of codeEl.children) { 302 if (isCodeAnnotation(childEl)) { 303 childEl.remove(); 304 } 305 } 306 return codeEl.innerText; 307 } 308 const clipboard = new window.ClipboardJS('.code-copy-button:not([data-in-quarto-modal])', { 309 text: getTextToCopy 310 }); 311 clipboard.on('success', onCopySuccess); 312 if (window.document.getElementById('quarto-embedded-source-code-modal')) { 313 // For code content inside modals, clipBoardJS needs to be initialized with a container option 314 // TODO: Check when it could be a function (https://github.com/zenorocha/clipboard.js/issues/860) 315 const clipboardModal = new window.ClipboardJS('.code-copy-button[data-in-quarto-modal]', { 316 text: getTextToCopy, 317 container: window.document.getElementById('quarto-embedded-source-code-modal') 318 }); 319 clipboardModal.on('success', onCopySuccess); 320 } 321 var localhostRegex = new RegExp(/^(?:http|https):\/\/localhost\:?[0-9]*\//); 322 var mailtoRegex = new RegExp(/^mailto:/); 323 var filterRegex = new RegExp("https:\/\/www\.mansthulin\.se"); 324 var isInternal = (href) => { 325 return filterRegex.test(href) || localhostRegex.test(href) || mailtoRegex.test(href); 326 } 327 // Inspect non-navigation links and adorn them if external 328 var links = window.document.querySelectorAll('a[href]:not(.nav-link):not(.navbar-brand):not(.toc-action):not(.sidebar-link):not(.sidebar-item-toggle):not(.pagination-link):not(.no-external):not([aria-hidden]):not(.dropdown-item):not(.quarto-navigation-tool):not(.about-link)'); 329 for (var i=0; i<links.length; i++) { 330 const link = links[i]; 331 if (!isInternal(link.href)) { 332 // undo the damage that might have been done by quarto-nav.js in the case of 333 // links that we want to consider external 334 if (link.dataset.originalHref !== undefined) { 335 link.href = link.dataset.originalHref; 336 } 337 } 338 } 339 function tippyHover(el, contentFn, onTriggerFn, onUntriggerFn) { 340 const config = { 341 allowHTML: true, 342 maxWidth: 500, 343 delay: 100,
344 arrow: false, 345 appendTo: function(el) { 346 return el.parentElement; 347 }, 348 interactive: true, 349 interactiveBorder: 10, 350 theme: 'quarto', 351 placement: 'bottom-start', 352 }; 353 if (contentFn) { 354 config.content = contentFn; 355 } 356 if (onTriggerFn) { 357 config.onTrigger = onTriggerFn; 358 } 359 if (onUntriggerFn) { 360 config.onUntrigger = onUntriggerFn; 361 } 362 window.tippy(el, config); 363 } 364 const noterefs = window.document.querySelectorAll('a[role="doc-noteref"]'); 365 for (var i=0; i<noterefs.length; i++) { 366 const ref = noterefs[i]; 367 tippyHover(ref, function() { 368 // use id or data attribute instead here 369 let href = ref.getAttribute('data-footnote-href') || ref.getAttribute('href'); 370 try { href = new URL(href).hash; } catch {} 371 const id = href.replace(/^#\/?/, ""); 372 const note = window.document.getElementById(id); 373 if (note) { 374 return note.innerHTML; 375 } else { 376 return ""; 377 } 378 }); 379 } 380 const xrefs = window.document.querySelectorAll('a.quarto-xref'); 381 const processXRef = (id, note) => { 382 // Strip column container classes 383 const stripColumnClz = (el) => { 384 el.classList.remove("page-full", "page-columns"); 385 if (el.children) { 386 for (const child of el.children) { 387 stripColumnClz(child); 388 } 389 } 390 } 391 stripColumnClz(note) 392 if (id === null || id.startsWith('sec-')) { 393 // Special case sections, only their first couple elements 394 const container = document.createElement("div"); 395 if (note.children && note.children.length > 2) { 396 container.appendChild(note.children[0].cloneNode(true)); 397 for (let i = 1; i < note.children.length; i++) { 398 const child = note.children[i]; 399 if (child.tagName === "P" && child.innerText === "") { 400 continue; 401 } else { 402 container.appendChild(child.cloneNode(true)); 403 break; 404 } 405 } 406 if (window.Quarto?.typesetMath) { 407 window.Quarto.typesetMath(container); 408 } 409 return container.innerHTML 410 } else { 411 if (window.Quarto?.typesetMath) { 412 window.Quarto.typesetMath(note); 413 } 414 return note.innerHTML; 415 } 416 } else { 417 // Remove any anchor links if they are present 418 const anchorLink = note.querySelector('a.anchorjs-link'); 419 if (anchorLink) { 420 anchorLink.remove(); 421 } 422 if (window.Quarto?.typesetMath) { 423 window.Quarto.typesetMath(note); 424 } 425 // TODO in 1.5, we should make sure this works without a callout special case 426 if (note.classList.contains("callout")) { 427 return note.outerHTML; 428 } else { 429 return note.innerHTML; 430 } 431 } 432 } 433 for (var i=0; i<xrefs.length; i++) { 434 const xref = xrefs[i]; 435 tippyHover(xref, undefined, function(instance) { 436 instance.disable(); 437 let url = xref.getAttribute('href'); 438 let hash = undefined; 439 if (url.startsWith('#')) { 440 hash = url; 441 } else { 442 try { hash = new URL(url).hash; } catch {} 443 } 444 if (hash) { 445 const id = hash.replace(/^#\/?/, ""); 446 const note = window.document.getElementById(id); 447 if (note !== null) { 448 try { 449 const html = processXRef(id, note.cloneNode(true)); 450 instance.setContent(html); 451 } finally { 452 instance.enable(); 453 instance.show(); 454 } 455 } else { 456 // See if we can fetch this 457 fetch(url.split('#')[0]) 458 .then(res => res.text()) 459 .then(html => { 460 const parser = new DOMParser(); 461 const htmlDoc = parser.parseFromString(html, "text/html"); 462 const note = htmlDoc.getElementById(id); 463 if (note !== null) { 464 const html = processXRef(id, note); 465 instance.setContent(html); 466 } 467 }).finally(() => { 468 instance.enable(); 469 instance.show(); 470 }); 471 } 472 } else { 473 // See if we can fetch a full url (with no hash to target)
474 // This is a special case and we should probably do some content thinning / targeting 475 fetch(url) 476 .then(res => res.text()) 477 .then(html => { 478 const parser = new DOMParser(); 479 const htmlDoc = parser.parseFromString(html, "text/html"); 480 const note = htmlDoc.querySelector('main.content'); 481 if (note !== null) { 482 // This should only happen for chapter cross references 483 // (since there is no id in the URL) 484 // remove the first header 485 if (note.children.length > 0 && note.children[0].tagName === "HEADER") { 486 note.children[0].remove(); 487 } 488 const html = processXRef(null, note); 489 instance.setContent(html); 490 } 491 }).finally(() => { 492 instance.enable(); 493 instance.show(); 494 }); 495 } 496 }, function(instance) { 497 }); 498 } 499 let selectedAnnoteEl; 500 const selectorForAnnotation = ( cell, annotation) => { 501 let cellAttr = 'data-code-cell="' + cell + '"'; 502 let lineAttr = 'data-code-annotation="' + annotation + '"'; 503 const selector = 'span[' + cellAttr + '][' + lineAttr + ']'; 504 return selector; 505 } 506 const selectCodeLines = (annoteEl) => { 507 const doc = window.document; 508 const targetCell = annoteEl.getAttribute("data-target-cell"); 509 const targetAnnotation = annoteEl.getAttribute("data-target-annotation"); 510 const annoteSpan = window.document.querySelector(selectorForAnnotation(targetCell, targetAnnotation)); 511 const lines = annoteSpan.getAttribute("data-code-lines").split(","); 512 const lineIds = lines.map((line) => { 513 return targetCell + "-" + line; 514 }) 515 let top = null; 516 let height = null; 517 let parent = null; 518 if (lineIds.length > 0) { 519 //compute the position of the single el (top and bottom and make a div) 520 const el = window.document.getElementById(lineIds[0]); 521 top = el.offsetTop; 522 height = el.offsetHeight; 523 parent = el.parentElement.parentElement; 524 if (lineIds.length > 1) { 525 const lastEl = window.document.getElementById(lineIds[lineIds.length - 1]); 526 const bottom = lastEl.offsetTop + lastEl.offsetHeight; 527 height = bottom - top; 528 } 529 if (top !== null && height !== null && parent !== null) { 530 // cook up a div (if necessary) and position it 531 let div = window.document.getElementById("code-annotation-line-highlight"); 532 if (div === null) { 533 div = window.document.createElement("div"); 534 div.setAttribute("id", "code-annotation-line-highlight"); 535 div.style.position = 'absolute'; 536 parent.appendChild(div); 537 } 538 div.style.top = top - 2 + "px"; 539 div.style.height = height + 4 + "px"; 540 div.style.left = 0; 541 let gutterDiv = window.document.getElementById("code-annotation-line-highlight-gutter"); 542 if (gutterDiv === null) { 543 gutterDiv = window.document.createElement("div"); 544 gutterDiv.setAttribute("id", "code-annotation-line-highlight-gutter"); 545 gutterDiv.style.position = 'absolute'; 546 const codeCell = window.document.getElementById(targetCell); 547 const gutter = codeCell.querySelector('.code-annotation-gutter'); 548 gutter.appendChild(gutterDiv); 549 } 550 gutterDiv.style.top = top - 2 + "px"; 551 gutterDiv.style.height = height + 4 + "px"; 552 } 553 selectedAnnoteEl = annoteEl; 554 } 555 }; 556 const unselectCodeLines = () => { 557 const elementsIds = ["code-annotation-line-highlight", "code-annotation-line-highlight-gutter"]; 558 elementsIds.forEach((elId) => { 559 const div = window.document.getElementById(elId); 560 if (div) { 561 div.remove(); 562 } 563 }); 564 selectedAnnoteEl = undefined; 565 }; 566 // Handle positioning of the toggle 567 window.addEventListener( 568 "resize", 569 throttle(() => { 570 elRect = undefined; 571 if (selectedAnnoteEl) { 572 selectCodeLines(selectedAnnoteEl); 573 } 574 }, 10) 575 ); 576 function throttle(fn, ms) { 577 let throttle = false;
578 let timer; 579 return (...args) => { 580 if(!throttle) { // first call gets through 581 fn.apply(this, args); 582 throttle = true; 583 } else { // all the others get throttled 584 if(timer) clearTimeout(timer); // cancel #2 585 timer = setTimeout(() => { 586 fn.apply(this, args); 587 timer = throttle = false; 588 }, ms); 589 } 590 }; 591 } 592 // Attach click handler to the DT 593 const annoteDls = window.document.querySelectorAll('dt[data-target-cell]'); 594 for (const annoteDlNode of annoteDls) { 595 annoteDlNode.addEventListener('click', (event) => { 596 const clickedEl = event.target; 597 if (clickedEl !== selectedAnnoteEl) { 598 unselectCodeLines(); 599 const activeEl = window.document.querySelector('dt[data-target-cell].code-annotation-active'); 600 if (activeEl) { 601 activeEl.classList.remove('code-annotation-active'); 602 } 603 selectCodeLines(clickedEl); 604 clickedEl.classList.add('code-annotation-active'); 605 } else { 606 // Unselect the line 607 unselectCodeLines(); 608 clickedEl.classList.remove('code-annotation-active'); 609 } 610 }); 611 } 612 const findCites = (el) => { 613 const parentEl = el.parentElement; 614 if (parentEl) { 615 const cites = parentEl.dataset.cites; 616 if (cites) { 617 return { 618 el, 619 cites: cites.split(' ') 620 }; 621 } else { 622 return findCites(el.parentElement) 623 } 624 } else { 625 return undefined; 626 } 627 }; 628 var bibliorefs = window.document.querySelectorAll('a[role="doc-biblioref"]'); 629 for (var i=0; i<bibliorefs.length; i++) { 630 const ref = bibliorefs[i]; 631 const citeInfo = findCites(ref); 632 if (citeInfo) { 633 tippyHover(citeInfo.el, function() { 634 var popup = window.document.createElement('div'); 635 citeInfo.cites.forEach(function(cite) { 636 var citeDiv = window.document.createElement('div'); 637 citeDiv.classList.add('hanging-indent'); 638 citeDiv.classList.add('csl-entry'); 639 var biblioDiv = window.document.getElementById('ref-' + cite); 640 if (biblioDiv) { 641 citeDiv.innerHTML = biblioDiv.innerHTML; 642 } 643 popup.appendChild(citeDiv); 644 }); 645 return popup.innerHTML; 646 }); 647 } 648 } 649}); 650</script>
650 651</div> <!-- /content --> 652<footer class="footer"> 653 <div class="nav-footer"> 654 <div class="nav-footer-left"> 655<p><span>© 2025 Måns Thulin</span></p> 656</div> 657 <div class="nav-footer-center"> 658<p><span>E-mail: <a href="mailto:[email protected]">[email protected]</a></span></p> 659</div> 660 <div class="nav-footer-right"> 661<p><span>Made with <a href="http://www.r-project.org"><iconify-icon role="img" inline="" icon="fa-brands:r-project" aria-label="Icon r-project from fa-brands Iconify.design set." title="Icon r-project from fa-brands Iconify.design set."></iconify-icon></a> and <a href="https://quarto.org/"><iconify-icon role="img" inline="" icon="simple-icons:quarto" aria-label="Icon quarto from simple-icons Iconify.design set." title="Icon quarto from simple-icons Iconify.design set."></iconify-icon></a></span></p> 662</div> 663 </div> 664</footer> 665 666 667 668 669</body></html>
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.