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.10.18"> 6 7<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes"> 8 9 10<title>About â Molecular Nodes</title> 11<style> 12/* Default styles provided by pandoc. 13** See https://pandoc.org/MANUAL.html#variables-for-html for config info. 14*/ 15span.smallcaps{font-variant: small-caps;} 16div.columns{display: flex; gap: 1.5em;} 17div.column{flex: auto;} 18@media screen { 19div.columns{gap: min(4vw, 1.5em);} 20div.column{overflow-x: auto;} 21} 22div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;} 23ul.task-list{list-style: none;} 24ul.task-list li input[type="checkbox"] { 25 width: 0.8em; 26 margin: 0 0.8em 0.2em -1em; /* quarto-specific, see https://github.com/quarto-dev/quarto-cli/issues/4556 */ 27 vertical-align: middle; 28} 29</style> 30 31
32<script src="site_libs/quarto-nav/quarto-nav.js"></script>
vendor: 1 bytes, line 32
32
33<script src="site_libs/quarto-nav/headroom.min.js"></script>
vendor: 1 bytes, line 33
33
34<script src="site_libs/clipboard/clipboard.min.js"></script>
vendor: 1 bytes, line 34
34
35<script src="site_libs/quarto-search/autocomplete.umd.js"></script>
vendor: 1 bytes, line 35
35
36<script src="site_libs/quarto-search/fuse.min.js"></script>
vendor: 1 bytes, line 36
36
37<script src="site_libs/quarto-search/quarto-search.js"></script>
37 38<meta name="quarto:offset" content="./">
39<script src="site_libs/quarto-html/quarto.js" type="module"></script>
vendor: 1 bytes, line 39
39
40<script src="site_libs/quarto-html/tabsets/tabsets.js" type="module"></script>
vendor: 1 bytes, line 40
40
41<script src="site_libs/quarto-html/popper.min.js"></script>
vendor: 1 bytes, line 41
41
42<script src="site_libs/quarto-html/tippy.umd.min.js"></script>
vendor: 1 bytes, line 42
42
43<script src="site_libs/quarto-html/anchor.min.js"></script>
43 44<link href="site_libs/quarto-html/tippy.css" rel="stylesheet"> 45<link href="site_libs/quarto-html/quarto-syntax-highlighting-dark-b85bc7c33db8c58e0ec93c3465666fed.css" rel="stylesheet" id="quarto-text-highlighting-styles">
46<script src="site_libs/bootstrap/bootstrap.min.js"></script>
46 47<link href="site_libs/bootstrap/bootstrap-icons.css" rel="stylesheet"> 48<link href="site_libs/bootstrap/bootstrap-cf537f47c02263b96802a5caadf76bdf.min.css" rel="stylesheet" append-hash="true" id="quarto-bootstrap" data-mode="dark">
49<script id="quarto-search-options" type="application/json">{ 50 "location": "navbar", 51 "copy-button": false, 52 "collapse-after": 3, 53 "panel-placement": "end", 54 "type": "overlay", 55 "limit": 50, 56 "keyboard-shortcut": [ 57 "f", 58 "/", 59 "s" 60 ], 61 "show-item-context": false, 62 "language": { 63 "search-no-results-text": "No results", 64 "search-matching-documents-text": "matching documents", 65 "search-copy-link-title": "Copy link to search", 66 "search-hide-matches-text": "Hide additional matches", 67 "search-more-match-text": "more match in this document", 68 "search-more-matches-text": "more matches in this document", 69 "search-clear-button-title": "Clear", 70 "search-text-placeholder": "", 71 "search-detached-cancel-button-title": "Cancel", 72 "search-submit-button-title": "Submit", 73 "search-label": "Search" 74 } 75}</script>
vendor: 67 bytes, lines 75-76
75 76<script async="" src="https://www.googletagmanager.com/gtag/js?id=
76G-W3K8TDXSCF
vendor: 13 bytes, lines 76-77
76"></script> 77
78<script type="text/javascript"> 79
vendor: 127 bytes, lines 80-83
80window.dataLayer = window.dataLayer || []; 81function gtag(){dataLayer.push(arguments);} 82gtag('js', new Date()); 83gtag('config', '
83G-W3K8TDXSCF
vendor: 29 bytes, line 83
83', { 'anonymize_ip': true});
84</script>
vendor: 1 bytes, line 84
84
85<script type="module"> 86 // Re-highlight code blocks with Shiki (the TextMate-grammar engine VS Code 87 // uses) for richer tokens than Pandoc's skylighting: function calls, 88 // attribute access, keyword arguments, etc. Pandoc's highlighting is kept 89 // in the HTML as a no-JS fallback and replaced here on load. 90 // 91 // To change colors, swap "dracula" for any theme from 92 // https://shiki.style/themes, or add/edit `tokenColors` rules below 93 // (scope reference: https://macromates.com/manual/en/language_grammars). 94 import { codeToHtml, bundledThemes } from "https://esm.sh/shiki@3"; 95 96 const dracula = (await bundledThemes["dracula-soft"]()).default; 97 const theme = { 98 ...dracula, 99 name: "dracula-nodebpy", 100 tokenColors: [ 101 ...dracula.tokenColors, 102 // Dracula ships no rule for Python call expressions; color them like 103 // function definitions so g.Cube() / tree.inputs.integer() pop. 104 { 105 scope: "meta.function-call.generic.python", 106 settings: { foreground: "#50FA7B" }, 107 }, 108 ], 109 }; 110 111 async function rehighlight(pre) { 112 const code = pre.querySelector("code"); 113 const lang = [...pre.classList].find( 114 (c) => !["sourceCode", "code-with-copy"].includes(c), 115 ); 116 if (!code || !lang) return; 117 try { 118 const html = await codeToHtml(code.textContent.replace(/\n$/, ""), { 119 lang, 120 theme, 121 }); 122 const shikiPre = new DOMParser() 123 .parseFromString(html, "text/html") 124 .querySelector("pre"); 125 code.innerHTML = shikiPre.querySelector("code").innerHTML; 126 pre.style.backgroundColor = shikiPre.style.backgroundColor; 127 pre.style.color = shikiPre.style.color; 128 } catch { 129 // Unknown language for Shiki â keep Pandoc's highlighting. 130 } 131 } 132 133 await Promise.all( 134 [...document.querySelectorAll("pre.sourceCode")].map(rehighlight), 135 ); 136</script>
136 137 138 139</head> 140 141<body class="nav-fixed fullcontent quarto-light"> 142 143<div id="quarto-search-results"></div> 144 <header id="quarto-header" class="headroom fixed-top"> 145 <nav class="navbar navbar-expand-lg " data-bs-theme="dark"> 146 <div class="navbar-container container-fluid"> 147 <div class="navbar-brand-container mx-auto"> 148 <a class="navbar-brand" href="./index.html"> 149 <span class="navbar-title">Molecular Nodes</span> 150 </a> 151 </div> 152 <div id="quarto-search" class="" title="Search"></div> 153 <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(); }"> 154 <span class="navbar-toggler-icon"></span> 155</button> 156 <div class="collapse navbar-collapse" id="navbarCollapse"> 157 <ul class="navbar-nav navbar-nav-scroll me-auto"> 158 <li class="nav-item"> 159 <a class="nav-link" href="./tutorials/index.html"> 160<span class="menu-text">Documentation</span></a> 161 </li> 162 <li class="nav-item"> 163 <a class="nav-link" href="./nodes/index.html"> 164<span class="menu-text">Nodes</span></a> 165 </li> 166 <li class="nav-item"> 167 <a class="nav-link" href="./api/"> 168<span class="menu-text">API</span></a> 169 </li> 170 <li class="nav-item"> 171 <a class="nav-link" href="./changelog.html"> 172<span class="menu-text">Changelog</span></a> 173 </li> 174 <li class="nav-item"> 175 <a class="nav-link" href="./examples/"> 176<span class="menu-text">Examples</span></a> 177 </li> 178 <li class="nav-item"> 179 <a class="nav-link" href="./citations/"> 180<span class="menu-text">Citations</span></a> 181 </li> 182</ul> 183 </div> <!-- /navcollapse --> 184 <div class="quarto-navbar-tools tools-wide"> 185 <a href="https://github.com/BradyAJohnston/MolecularNodes" title="" class="quarto-navigation-tool px-1" aria-label=""><i class="bi bi-github"></i></a> 186 <a href="https://twitter.com/bradyajohnston" title="" class="quarto-navigation-tool px-1" aria-label=""><i class="bi bi-twitter"></i></a> 187 <a href="https://youtube.com/c/bradyjohnston" title="" class="quarto-navigation-tool px-1" aria-label=""><i class="bi bi-youtube"></i></a> 188</div> 189 </div> <!-- /container-fluid --> 190 </nav> 191</header> 192<!-- content --> 193<div id="quarto-content" class="quarto-container page-columns page-rows-contents page-layout-article page-navbar"> 194<!-- sidebar --> 195<!-- margin-sidebar --> 196 197<!-- main --> 198<main class="content" id="quarto-document-content"> 199 200<header id="title-block-header" class="quarto-title-block default"> 201<div class="quarto-title"> 202<h1 class="title">About</h1> 203</div> 204 205 206 207<div class="quarto-title-meta"> 208 209 210 211 212 </div> 213 214 215 216</header> 217 218 219<p><img src="images/logo.png" align="right" style="height:250px;"></p> 220<p><img src="https://github.com/bradyajohnston/molecularnodes/actions/workflows/docs.yml/badge.svg" class="img-fluid" alt="Documentation Building"> <img src="https://github.com/bradyajohnston/molecularnodes/actions/workflows/tests.yml/badge.svg" class="img-fluid" alt="Running Tests"> <a href="https://codecov.io/gh/BradyAJohnston/MolecularNodes"><img src="https://codecov.io/gh/BradyAJohnston/MolecularNodes/branch/main/graph/badge.svg?token=ZB2SJFY8FU" class="img-fluid" alt="codecov"></a></p> 221<p><a href="https://www.github.com/bradyajohnston/MolecularNodes/releases"><img src="https://img.shields.io/github/v/release/bradyajohnston/molecularnodes" alt="Badge displaying license, which is MIT." style="height:20px"></a> <a href="https://www.github.com/bradyajohnston/MolecularNodes/releases"><img src="https://img.shields.io/github/downloads/BradyAJohnston/MolecularNodes/total.svg" alt="Repo total downloads count." style="height:20px"></a> <a href="https://www.buymeacoffee.com/bradyajohnston"><img src="https://img.shields.io/github/license/bradyajohnston/molecularnodes" alt="Badge displaying license, which is MIT." style="height:20px"></a> <a href="https://www.buymeacoffee.com/bradyajohnston"><img src="https://img.shields.io/github/stars/bradyajohnston/molecularnodes?style=social" alt="Badge displaying count of GitHub stars." style="height:20px"></a></p> 222<p><a href="https://pypi.org/project/biotite"><img src="https://img.shields.io/badge/powered%20by-Biotite-orange.svg" alt="Badge showing usage of MDAnalysis as a python package powering the add-on" style="height:20px"></a> <a href="https://pypi.org/project/MDAnalysis"><img src="https://img.shields.io/badge/powered%20by-MDAnalysis-orange.svg" alt="Badge showing usage of biotite as a python package powering the add-on" style="height:20px"></a></p> 223<p><a href="https://patreon.com/bradyajohnston"><img src="https://img.shields.io/endpoint.svg?url=https%3A%2F%2Fshieldsio-patreon.vercel.app%2Fapi%3Fusername%3Dbradyajohnston%26type%3Dpatrons&style=for-the-badge" alt="Support me on Patreon with ongoing donations" style="height:35px"></a></p> 224<p><a href="https://buymeacoffee.com/bradyajohnston"><img src="https://img.shields.io/badge/Buy%20Me%20a%20Coffee-ffdd00?style=for-the-badge&logo=buy-me-a-coffee&logoColor=black" alt="Support me by buying a couple of coffees as a one-off donation" style="height:35px"></a></p> 225<p><a href="https://discord.gg/fvw6vT3vY9"><img src="https://img.shields.io/badge/Discord-%235865F2.svg?style=for-the-badge&logo=discord&logoColor=white" alt="Join the scientific visualisation in Blender discord." style="height:35px"></a></p> 226<p>Molecular Nodes (MN) is an addon for the 3D modelling & animation program <a href="https://blender.org">Blender</a>. MN enables easy import of molecular data such as <code>.pdb</code> & <code>
226.mmCIF</code>, along with a variety of molecular dynamics trajectories and topologies from a variety of simulation sources. Other data formats including electron microscopy (EM) <code>.map</code>, EM tomography files such as <code>.map</code> and <code>.star</code>, with the potential to support more data formats as well.</p> 227<p>See the talk below at the 2022 Blender conference for an overview of the add-on itâs capabilities.</p> 228<div class="center"> 229<iframe width="560" height="315" src="https://www.youtube.com/embed/adhTmwYwOiA?si=em6xRime7HZ8L44T" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen=""> 230</iframe> 231</div> 232<p>Molecular Nodes provides the translation layer that allows importing of molecular data formats, while Blender provides the industry-leading animation and rendering tools to create visually stunning molecular graphics with ease.</p> 233<p>The add-on runs on the <a href="https://docs.blender.org/manual/en/latest/modeling/geometry_nodes/introduction.html">
233Geometry Nodes</a> procedural modelling and animation system inside of Blender, which gives the add-on speed and robustness with minimal user input required.</p> 234<table class="caption-top table"> 235<colgroup> 236<col style="width: 33%"> 237<col style="width: 33%"> 238<col style="width: 33%"> 239</colgroup> 240<thead> 241<tr class="header"> 242<th>Procedural Animations</th> 243<th>Different Stylings</th> 244<th>MD Trajectories</th> 245</tr> 246</thead> 247<tbody> 248<tr class="odd"> 249<td><img src="images/index/mn-cartoon-example.gif" class="img-fluid" width="400"></td> 250<td><img src="images/index/mn-md-example-spike.gif" class="img-fluid"></td> 251<td><img src="images/index/mn-md-example.gif" class="img-fluid"></td> 252</tr> 253<tr class="even"> 254<td>EM Density maps</td> 255<td><code>.star</code> mapbacks</td> 256<td></td> 257</tr> 258<tr class="odd"> 259<td><img src="images/index/mn-example-em.gif" class="img-fluid"></td> 260<td><video src="images/index/mn-example-starfile.mp4" class="img-fluid" controls=""><a href="images/index/mn-example-starfile.mp4">Video</a></video></td> 261<td></td> 262</tr> 263</tbody> 264</table> 265<section id="getting-started" class="level2"> 266<h2 class="anchored" data-anchor-id="getting-started">Getting Started</h2> 267<p>To get started, checkout the <a href="#installation">installation page</a> for detailed instructions on how to install the add-on. Next check out some of the tutorials, such as <a href="./tutorials/interface.html">intro to blender</a>, <a href="./tutorials/importing.html">basics of downloading</a> from the PDB or how to <a href="./tutorials/selections.html">customise selections</a>.</p> 268<p>There are some <em>outdated</em> tutorials on YouTube about how to use the add-on also. MN has been developed further and improved since these videos, but the general workflow remains the same.</p> 269<div class="center"> 270<iframe width="560" height="315" src="https://www.youtube.com/embed/CvmFaRVmZRU" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""> 271</iframe> 272</div> 273 274 275</section> 276 277</main> <!-- /main -->
278<script id="quarto-html-after-body" type="application/javascript"> 279 window.document.addEventListener("DOMContentLoaded", function (event) { 280 const icon = "î§"; 281 const anchorJS = new window.AnchorJS(); 282 anchorJS.options = { 283 placement: 'right', 284 icon: icon 285 }; 286 anchorJS.add('.anchored'); 287 const isCodeAnnotation = (el) => { 288 for (const clz of el.classList) { 289 if (clz.startsWith('code-annotation-')) { 290 return true; 291 } 292 } 293 return false; 294 } 295 const onCopySuccess = function(e) { 296 // button target 297 const button = e.trigger; 298 // don't keep focus 299 button.blur(); 300 // flash "checked" 301 button.classList.add('code-copy-button-checked'); 302 var currentTitle = button.getAttribute("title"); 303 button.setAttribute("title", "Copied!"); 304 let tooltip; 305 if (window.bootstrap) { 306 button.setAttribute("data-bs-toggle", "tooltip"); 307 button.setAttribute("data-bs-placement", "left"); 308 button.setAttribute("data-bs-title", "Copied!"); 309 tooltip = new bootstrap.Tooltip(button, 310 { trigger: "manual", 311 customClass: "code-copy-button-tooltip", 312 offset: [0, -8]}); 313 tooltip.show(); 314 } 315 setTimeout(function() { 316 if (tooltip) { 317 tooltip.hide(); 318 button.removeAttribute("data-bs-title"); 319 button.removeAttribute("data-bs-toggle"); 320 button.removeAttribute("data-bs-placement"); 321 } 322 button.setAttribute("title", currentTitle); 323 button.classList.remove('code-copy-button-checked'); 324 }, 1000); 325 // clear code selection 326 e.clearSelection(); 327 } 328 const getTextToCopy = function(trigger) { 329 const outerScaffold = trigger.parentElement.cloneNode(true); 330 const codeEl = outerScaffold.querySelector('code'); 331 for (const childEl of codeEl.children) { 332 if (isCodeAnnotation(childEl)) { 333 childEl.remove(); 334 } 335 } 336 return codeEl.innerText; 337 } 338 const clipboard = new window.ClipboardJS('.code-copy-button:not([data-in-quarto-modal])', { 339 text: getTextToCopy 340 }); 341 clipboard.on('success', onCopySuccess); 342 if (window.document.getElementById('quarto-embedded-source-code-modal')) { 343 const clipboardModal = new window.ClipboardJS('.code-copy-button[data-in-quarto-modal]', { 344 text: getTextToCopy, 345 container: window.document.getElementById('quarto-embedded-source-code-modal') 346 }); 347 clipboardModal.on('success', onCopySuccess); 348 } 349 var localhostRegex = new RegExp(/^(?:http|https):\/\/localhost\:?[0-9]*\//); 350 var mailtoRegex = new RegExp(/^mailto:/); 351 var filterRegex = new RegExp('/' + window.location.host + '/'); 352 var isInternal = (href) => { 353 return filterRegex.test(href) || localhostRegex.test(href) || mailtoRegex.test(href); 354 } 355 // Inspect non-navigation links and adorn them if external 356 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)'); 357 for (var i=0; i<links.length; i++) { 358 const link = links[i]; 359 if (!isInternal(link.href)) { 360 // undo the damage that might have been done by quarto-nav.js in the case of 361 // links that we want to consider external 362 if (link.dataset.originalHref !== undefined) { 363 link.href = link.dataset.originalHref; 364 } 365 } 366 } 367 function tippyHover(el, contentFn, onTriggerFn, onUntriggerFn) { 368 const config = { 369 allowHTML: true, 370 maxWidth: 500, 371 delay: 100, 372 arrow: false, 373 appendTo: function(el) { 374 return el.parentElement; 375 }, 376 interactive: true, 377 interactiveBorder: 10, 378 theme: 'quarto', 379 placement: 'bottom-start', 380 }; 381 if (contentFn) { 382 config.content = contentFn; 383 } 384 if (onTriggerFn) { 385 config.onTrigger = onTriggerFn; 386 } 387 if (onUntriggerFn) { 388 config.onUntrigger = onUntriggerFn; 389 } 390 window.tippy(el, config); 391 } 392 const noterefs = window.document.querySelectorAll('a[role="doc-noteref"]'); 393 for (var i=0; i<noterefs.length; i++) { 394 const ref = noterefs[i]; 395 tippyHover(ref, function() { 396 // use id or data attribute instead here 397 let href = ref.getAttribute('data-footnote-href') || ref.getAttribute('href'); 398 try { href = new URL(href).hash; } catch {} 399 const id = href.replace(/^#\/?/, ""); 400 const note = window.document.getElementById(id); 401 if (note) { 402 return note.innerHTML; 403 } else { 404 return ""; 405 } 406 }); 407 } 408 const xrefs = window.document.querySelectorAll('a.quarto-xref'); 409 const processXRef = (id, note) => { 410 // Strip column container classes 411 const stripColumnClz = (el) => { 412 el.classList.remove("page-full", "page-columns"); 413 if (el.children) { 414 for (const child of el.children) {
415 stripColumnClz(child); 416 } 417 } 418 } 419 stripColumnClz(note) 420 if (id === null || id.startsWith('sec-')) { 421 // Special case sections, only their first couple elements 422 const container = document.createElement("div"); 423 if (note.children && note.children.length > 2) { 424 container.appendChild(note.children[0].cloneNode(true)); 425 for (let i = 1; i < note.children.length; i++) { 426 const child = note.children[i]; 427 if (child.tagName === "P" && child.innerText === "") { 428 continue; 429 } else { 430 container.appendChild(child.cloneNode(true)); 431 break; 432 } 433 } 434 if (window.Quarto?.typesetMath) { 435 window.Quarto.typesetMath(container); 436 } 437 return container.innerHTML 438 } else { 439 if (window.Quarto?.typesetMath) { 440 window.Quarto.typesetMath(note); 441 } 442 return note.innerHTML; 443 } 444 } else { 445 // Remove any anchor links if they are present 446 const anchorLink = note.querySelector('a.anchorjs-link'); 447 if (anchorLink) { 448 anchorLink.remove(); 449 } 450 if (window.Quarto?.typesetMath) { 451 window.Quarto.typesetMath(note); 452 } 453 if (note.classList.contains("callout")) { 454 return note.outerHTML; 455 } else { 456 return note.innerHTML; 457 } 458 } 459 } 460 for (var i=0; i<xrefs.length; i++) { 461 const xref = xrefs[i]; 462 tippyHover(xref, undefined, function(instance) { 463 instance.disable(); 464 let url = xref.getAttribute('href'); 465 let hash = undefined; 466 if (url.startsWith('#')) { 467 hash = url; 468 } else { 469 try { hash = new URL(url).hash; } catch {} 470 } 471 if (hash) { 472 const id = hash.replace(/^#\/?/, ""); 473 const note = window.document.getElementById(id); 474 if (note !== null) { 475 try { 476 const html = processXRef(id, note.cloneNode(true)); 477 instance.setContent(html); 478 } finally { 479 instance.enable(); 480 instance.show(); 481 } 482 } else { 483 // See if we can fetch this 484 fetch(url.split('#')[0]) 485 .then(res => res.text()) 486 .then(html => { 487 const parser = new DOMParser(); 488 const htmlDoc = parser.parseFromString(html, "text/html"); 489 const note = htmlDoc.getElementById(id); 490 if (note !== null) { 491 const html = processXRef(id, note); 492 instance.setContent(html); 493 } 494 }).finally(() => { 495 instance.enable(); 496 instance.show(); 497 }); 498 } 499 } else { 500 // See if we can fetch a full url (with no hash to target) 501 // This is a special case and we should probably do some content thinning / targeting 502 fetch(url) 503 .then(res => res.text()) 504 .then(html => { 505 const parser = new DOMParser(); 506 const htmlDoc = parser.parseFromString(html, "text/html"); 507 const note = htmlDoc.querySelector('main.content'); 508 if (note !== null) { 509 // This should only happen for chapter cross references 510 // (since there is no id in the URL) 511 // remove the first header 512 if (note.children.length > 0 && note.children[0].tagName === "HEADER") { 513 note.children[0].remove(); 514 } 515 const html = processXRef(null, note); 516 instance.setContent(html); 517 } 518 }).finally(() => { 519 instance.enable(); 520 instance.show(); 521 }); 522 } 523 }, function(instance) { 524 }); 525 } 526 let selectedAnnoteEl; 527 const selectorForAnnotation = ( cell, annotation) => { 528 let cellAttr = 'data-code-cell="' + cell + '"'; 529 let lineAttr = 'data-code-annotation="' + annotation + '"'; 530 const selector = 'span[' + cellAttr + '][' + lineAttr + ']'; 531 return selector; 532 } 533 const selectCodeLines = (annoteEl) => { 534 const doc = window.document; 535 const targetCell = annoteEl.getAttribute("data-target-cell"); 536 const targetAnnotation = annoteEl.getAttribute("data-target-annotation"); 537 const annoteSpan = window.document.querySelector(selectorForAnnotation(targetCell, targetAnnotation)); 538 const lines = annoteSpan.getAttribute("data-code-lines").split(","); 539 const lineIds = lines.map((line) => { 540 return targetCell + "-" + line; 541 }) 542 let top = null; 543 let height = null; 544 let parent = null; 545 if (lineIds.length > 0) { 546 //compute the position of the single el (top and bottom and make a div) 547 const el = window.document.getElementById(lineIds[0]); 548 top = el.offsetTop; 549 height = el.offsetHeight; 550 parent = el.parentElement.parentElement; 551 if (lineIds.length > 1) { 552 const lastEl = window.document.getElementById(lineIds[lineIds.length - 1]); 553 const bottom = lastEl.offsetTop + lastEl.offsetHeight; 554 height = bottom - top; 555 } 556 if (top !== null && height !== null && parent !== null) { 557 // cook up a div (if necessary) and position it 558 let div = window.document.getElementById("code-annotation-line-highlight"); 559 if (div === null) { 560 div = window.document.createElement("div"); 561 div.setAttribute("id", "code-annotation-line-highlight"); 562 div.style.position = 'absolute'; 563 parent.appendChild(div); 564 } 565 div.style.top = top - 2 + "px"; 566 div.style.height = height + 4 + "px"; 567 div.style.left = 0; 568 let gutterDiv = window.document.getElementById("code-annotation-line-highlight-gutter"); 569 if (gutterDiv === null) { 570 gutterDiv = window.document.createElement("div"); 571 gutterDiv.setAttribute("id", "code-annotation-line-highlight-gutter"); 572 gutterDiv.style.position = 'absolute'; 573 const codeCell = window.document.getElementById(targetCell); 574 const gutter = codeCell.querySelector('.code-annotation-gutter'); 575 gutter.appendChild(gutterDiv); 576 } 577 gutterDiv.style.top = top - 2 + "px"; 578 gutterDiv.style.height = height + 4 + "px"; 579 } 580 selectedAnnoteEl = annoteEl; 581 } 582 }; 583 const unselectCodeLines = () => { 584 const elementsIds = ["code-annotation-line-highlight", "code-annotation-line-highlight-gutter"]; 585 elementsIds.forEach((elId) => { 586 const div = window.document.getElementById(elId); 587 if (div) { 588 div.remove(); 589 } 590 }); 591 selectedAnnoteEl = undefined; 592 }; 593 // Handle positioning of the toggle 594 window.addEventListener( 595 "resize", 596 throttle(() => { 597 elRect = undefined; 598 if (selectedAnnoteEl) { 599 selectCodeLines(selectedAnnoteEl); 600 } 601 }, 10) 602 ); 603 function throttle(fn, ms) { 604 let throttle = false;
605 let timer; 606 return (...args) => { 607 if(!throttle) { // first call gets through 608 fn.apply(this, args); 609 throttle = true; 610 } else { // all the others get throttled 611 if(timer) clearTimeout(timer); // cancel #2 612 timer = setTimeout(() => { 613 fn.apply(this, args); 614 timer = throttle = false; 615 }, ms); 616 } 617 }; 618 } 619 // Attach click handler to the DT 620 const annoteDls = window.document.querySelectorAll('dt[data-target-cell]'); 621 for (const annoteDlNode of annoteDls) { 622 annoteDlNode.addEventListener('click', (event) => { 623 const clickedEl = event.target; 624 if (clickedEl !== selectedAnnoteEl) { 625 unselectCodeLines(); 626 const activeEl = window.document.querySelector('dt[data-target-cell].code-annotation-active'); 627 if (activeEl) { 628 activeEl.classList.remove('code-annotation-active'); 629 } 630 selectCodeLines(clickedEl); 631 clickedEl.classList.add('code-annotation-active'); 632 } else { 633 // Unselect the line 634 unselectCodeLines(); 635 clickedEl.classList.remove('code-annotation-active'); 636 } 637 }); 638 } 639 const findCites = (el) => { 640 const parentEl = el.parentElement; 641 if (parentEl) { 642 const cites = parentEl.dataset.cites; 643 if (cites) { 644 return { 645 el, 646 cites: cites.split(' ') 647 }; 648 } else { 649 return findCites(el.parentElement) 650 } 651 } else { 652 return undefined; 653 } 654 }; 655 var bibliorefs = window.document.querySelectorAll('a[role="doc-biblioref"]'); 656 for (var i=0; i<bibliorefs.length; i++) { 657 const ref = bibliorefs[i]; 658 const citeInfo = findCites(ref); 659 if (citeInfo) { 660 tippyHover(citeInfo.el, function() { 661 var popup = window.document.createElement('div'); 662 citeInfo.cites.forEach(function(cite) { 663 var citeDiv = window.document.createElement('div'); 664 citeDiv.classList.add('hanging-indent'); 665 citeDiv.classList.add('csl-entry'); 666 var biblioDiv = window.document.getElementById('ref-' + cite); 667 if (biblioDiv) { 668 citeDiv.innerHTML = biblioDiv.innerHTML; 669 } 670 popup.appendChild(citeDiv); 671 }); 672 return popup.innerHTML; 673 }); 674 } 675 } 676 }); 677 </script>
677 678</div> <!-- /content --> 679<footer class="footer"> 680 <div class="nav-footer"> 681 <div class="nav-footer-left"> 682<p>This site is built with â¤ï¸ and <a href="https://quarto.org/">Quarto</a>.</p> 683</div> 684 <div class="nav-footer-center"> 685 686 </div> 687 <div class="nav-footer-right"> 688 689 </div> 690 </div> 691</footer> 692 693 694 695 696</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.