Mercurial > repos > galaxy-australia > alphafold2
comparison scripts/alphafold.html @ 20:6ab1a261520a draft
planemo upload for repository https://github.com/usegalaxy-au/tools-au commit c3a90eb12ada44d477541baa4dd6182be29cd554-dirty
| author | galaxy-australia |
|---|---|
| date | Sun, 28 Jul 2024 20:09:55 +0000 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| 19:2f7702fd0a4c | 20:6ab1a261520a |
|---|---|
| 1 <!DOCTYPE html> | |
| 2 <html lang="en" dir="ltr"> | |
| 3 | |
| 4 <head> | |
| 5 <meta charset="utf-8"> | |
| 6 <meta http-equiv="X-UA-Compatible" content="IE=edge"> | |
| 7 <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| 8 | |
| 9 <title> Alphafold structure prediction </title> | |
| 10 | |
| 11 <link rel="preconnect" href="https://fonts.googleapis.com"> | |
| 12 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> | |
| 13 <link href="https://fonts.googleapis.com/css2?family=Ubuntu:wght@300;400;500;700&display=swap" rel="stylesheet"> | |
| 14 <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> | |
| 15 <script src="https://cdnjs.cloudflare.com/ajax/libs/chroma-js/2.1.0/chroma.min.js" integrity="sha512-yocoLferfPbcwpCMr8v/B0AB4SWpJlouBwgE0D3ZHaiP1nuu5djZclFEIj9znuqghaZ3tdCMRrreLoM8km+jIQ==" crossorigin="anonymous"></script> | |
| 16 | |
| 17 <style type="text/css"> | |
| 18 * { | |
| 19 margin: 0; | |
| 20 padding: 0; | |
| 21 } | |
| 22 html, body { | |
| 23 width: 100%; | |
| 24 font-size: 1rem; | |
| 25 } | |
| 26 body { | |
| 27 font-family: 'Ubuntu', sans-serif; | |
| 28 } | |
| 29 canvas { | |
| 30 background-color: white; | |
| 31 } | |
| 32 h1, h2, h3, h4, h5, h6 { | |
| 33 color: dodgerblue; | |
| 34 text-align: center; | |
| 35 font-weight: lighter; | |
| 36 } | |
| 37 h1 { | |
| 38 margin: 2rem; | |
| 39 font-size: 3rem; | |
| 40 } | |
| 41 h2 { | |
| 42 font-size: 2rem; | |
| 43 margin-top: 1rem; | |
| 44 margin-bottom: .5rem; | |
| 45 } | |
| 46 button.btn { | |
| 47 color: #ccc; | |
| 48 margin: 1rem; | |
| 49 padding: .5rem; | |
| 50 font-size: 1rem; | |
| 51 min-width: 4rem; | |
| 52 border: none; | |
| 53 border-radius: .5rem; | |
| 54 background-color: grey; | |
| 55 transition-duration: 0.25s; | |
| 56 cursor: pointer; | |
| 57 } | |
| 58 button.btn.selected { | |
| 59 color: #eee; | |
| 60 background-color: dodgerblue; | |
| 61 } | |
| 62 button.btn.green { | |
| 63 color: #eee; | |
| 64 background-color: #10941f; | |
| 65 } | |
| 66 button.btn:focus { | |
| 67 outline: none; | |
| 68 color: inherit; | |
| 69 } | |
| 70 button.btn:hover { | |
| 71 color: white; | |
| 72 box-shadow: 0 0 10px dodgerblue; | |
| 73 } | |
| 74 button.btn.green:hover { | |
| 75 color: white; | |
| 76 box-shadow: 0 0 10px limegreen; | |
| 77 } | |
| 78 .main { | |
| 79 min-height: 90vh; | |
| 80 position: relative; | |
| 81 } | |
| 82 .flex { | |
| 83 display: flex; | |
| 84 justify-content: center; | |
| 85 align-items: center; | |
| 86 padding: 1rem; | |
| 87 } | |
| 88 .col { | |
| 89 flex-direction: column; | |
| 90 flex-grow: 0; | |
| 91 } | |
| 92 .controls { | |
| 93 padding-bottom: 10vh; | |
| 94 } | |
| 95 .box { | |
| 96 padding: .5rem 1rem; | |
| 97 margin: .5rem auto; | |
| 98 width: fit-content; | |
| 99 border-radius: 1rem; | |
| 100 } | |
| 101 .mono { | |
| 102 font-family: monospace; | |
| 103 color: #555; | |
| 104 background-color: #ddd; | |
| 105 padding: .25rem; | |
| 106 border-radius: .25rem; | |
| 107 } | |
| 108 .space-1 { | |
| 109 line-height: 1.2; | |
| 110 } | |
| 111 .space-2 { | |
| 112 line-height: 1.5; | |
| 113 } | |
| 114 .relative { | |
| 115 position: relative; | |
| 116 } | |
| 117 .legend { | |
| 118 max-width: 350px; | |
| 119 } | |
| 120 .legend .scale { | |
| 121 display: flex; | |
| 122 flex-direction: column; | |
| 123 align-items: center; | |
| 124 } | |
| 125 .legend .color { | |
| 126 width: 150px; | |
| 127 height: 30px; | |
| 128 justify-content: space-between; | |
| 129 background: linear-gradient( | |
| 130 90deg, | |
| 131 rgba(255,55,0,1) 0%, | |
| 132 rgba(216,224,6,1) 33%, | |
| 133 rgba(34,213,238,1) 66%, | |
| 134 rgba(3,30,148,1) 100% | |
| 135 ); | |
| 136 } | |
| 137 .legend .ticks { | |
| 138 margin-top: -1rem; | |
| 139 width: 180px; | |
| 140 justify-content: space-between; | |
| 141 } | |
| 142 #ngl-root-parent { | |
| 143 width: 40vw; | |
| 144 height: 30vw; | |
| 145 margin: auto; | |
| 146 position: relative; | |
| 147 } | |
| 148 #ngl-root { | |
| 149 width: 40vw; | |
| 150 height: 30vw; | |
| 151 border-radius: 15px; | |
| 152 border: 1px solid grey; | |
| 153 } | |
| 154 #ngl-nothing { | |
| 155 position: absolute; | |
| 156 top: 0; | |
| 157 left: 0; | |
| 158 display: none; | |
| 159 text-align: center; | |
| 160 width: 40vw; | |
| 161 height: 30vw; | |
| 162 padding-top: 12vw; | |
| 163 } | |
| 164 #ngl-loading { | |
| 165 position: absolute; | |
| 166 top: 0; | |
| 167 left: 0; | |
| 168 display: flex; | |
| 169 justify-content: center; | |
| 170 align-items: center; | |
| 171 width: 800px; | |
| 172 height: 600px; | |
| 173 width: 40vw; | |
| 174 height: 30vw; | |
| 175 } | |
| 176 #ngl-loading svg { | |
| 177 width: 30%; | |
| 178 height: 30%; | |
| 179 width: 10vw; | |
| 180 height: 10vw; | |
| 181 } | |
| 182 | |
| 183 /* Responsive */ | |
| 184 @media (max-width: 1400px) { | |
| 185 :root { | |
| 186 font-size: 10pt; | |
| 187 } | |
| 188 button.btn { | |
| 189 margin: .5rem; | |
| 190 padding: .25rem; | |
| 191 } | |
| 192 .box { | |
| 193 padding: .5rem; | |
| 194 margin: .5rem auto; | |
| 195 } | |
| 196 .legend { | |
| 197 max-width: 200px; | |
| 198 } | |
| 199 .help-text { | |
| 200 font-size: 0.8rem; | |
| 201 } | |
| 202 .mono { | |
| 203 padding: .25rem .5rem; | |
| 204 } | |
| 205 } | |
| 206 @media (max-width: 1000px) { | |
| 207 :root { | |
| 208 font-size: 8pt; | |
| 209 } | |
| 210 } | |
| 211 @media (max-width: 800px) { | |
| 212 :root { | |
| 213 font-size: 6pt; | |
| 214 } | |
| 215 } | |
| 216 </style> | |
| 217 | |
| 218 <script src="https://cdn.rawgit.com/arose/ngl/v2.0.0-dev.37/dist/ngl.js"></script> | |
| 219 </head> | |
| 220 | |
| 221 | |
| 222 <body> | |
| 223 <h1> Alphafold structure prediction </h1> | |
| 224 | |
| 225 <div class="main flex"> | |
| 226 <div class="col relative"> | |
| 227 <div id="ngl-root-parent"> | |
| 228 | |
| 229 <div id="ngl-root"></div> | |
| 230 | |
| 231 <div id="ngl-nothing"> | |
| 232 Select a representation to display | |
| 233 </div> | |
| 234 | |
| 235 <div id="ngl-loading"> | |
| 236 <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="margin: auto; background: none; display: block; shape-rendering: auto;" width="200px" height="200px" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid"> | |
| 237 <g transform="rotate(0 50 50)"> | |
| 238 <rect x="47" y="24" rx="3" ry="6" width="6" height="12" fill="#88879e"> | |
| 239 <animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.9166666666666666s" repeatCount="indefinite"></animate> | |
| 240 </rect> | |
| 241 </g><g transform="rotate(30 50 50)"> | |
| 242 <rect x="47" y="24" rx="3" ry="6" width="6" height="12" fill="#88879e"> | |
| 243 <animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.8333333333333334s" repeatCount="indefinite"></animate> | |
| 244 </rect> | |
| 245 </g><g transform="rotate(60 50 50)"> | |
| 246 <rect x="47" y="24" rx="3" ry="6" width="6" height="12" fill="#88879e"> | |
| 247 <animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.75s" repeatCount="indefinite"></animate> | |
| 248 </rect> | |
| 249 </g><g transform="rotate(90 50 50)"> | |
| 250 <rect x="47" y="24" rx="3" ry="6" width="6" height="12" fill="#88879e"> | |
| 251 <animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.6666666666666666s" repeatCount="indefinite"></animate> | |
| 252 </rect> | |
| 253 </g><g transform="rotate(120 50 50)"> | |
| 254 <rect x="47" y="24" rx="3" ry="6" width="6" height="12" fill="#88879e"> | |
| 255 <animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.5833333333333334s" repeatCount="indefinite"></animate> | |
| 256 </rect> | |
| 257 </g><g transform="rotate(150 50 50)"> | |
| 258 <rect x="47" y="24" rx="3" ry="6" width="6" height="12" fill="#88879e"> | |
| 259 <animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.5s" repeatCount="indefinite"></animate> | |
| 260 </rect> | |
| 261 </g><g transform="rotate(180 50 50)"> | |
| 262 <rect x="47" y="24" rx="3" ry="6" width="6" height="12" fill="#88879e"> | |
| 263 <animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.4166666666666667s" repeatCount="indefinite"></animate> | |
| 264 </rect> | |
| 265 </g><g transform="rotate(210 50 50)"> | |
| 266 <rect x="47" y="24" rx="3" ry="6" width="6" height="12" fill="#88879e"> | |
| 267 <animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.3333333333333333s" repeatCount="indefinite"></animate> | |
| 268 </rect> | |
| 269 </g><g transform="rotate(240 50 50)"> | |
| 270 <rect x="47" y="24" rx="3" ry="6" width="6" height="12" fill="#88879e"> | |
| 271 <animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.25s" repeatCount="indefinite"></animate> | |
| 272 </rect> | |
| 273 </g><g transform="rotate(270 50 50)"> | |
| 274 <rect x="47" y="24" rx="3" ry="6" width="6" height="12" fill="#88879e"> | |
| 275 <animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.16666666666666666s" repeatCount="indefinite"></animate> | |
| 276 </rect> | |
| 277 </g><g transform="rotate(300 50 50)"> | |
| 278 <rect x="47" y="24" rx="3" ry="6" width="6" height="12" fill="#88879e"> | |
| 279 <animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.08333333333333333s" repeatCount="indefinite"></animate> | |
| 280 </rect> | |
| 281 </g><g transform="rotate(330 50 50)"> | |
| 282 <rect x="47" y="24" rx="3" ry="6" width="6" height="12" fill="#88879e"> | |
| 283 <animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="0s" repeatCount="indefinite"></animate> | |
| 284 </rect> | |
| 285 </g> | |
| 286 </svg> | |
| 287 </div> | |
| 288 </div> | |
| 289 | |
| 290 <div class="flex"> | |
| 291 <div class="box space-1"> | |
| 292 <p> | |
| 293 <span class="mono">Scroll up/down</span> | |
| 294 to zoom in and out | |
| 295 </p> | |
| 296 <p> | |
| 297 <span class="mono">Click + drag</span> | |
| 298 to rotate the structure | |
| 299 </p> | |
| 300 <p> | |
| 301 <span class="mono">CTRL + click + drag</span> | |
| 302 to move the structure | |
| 303 </p> | |
| 304 <p> | |
| 305 <span class="mono">Click</span> | |
| 306 an atom to bring it into focus | |
| 307 </p> | |
| 308 </div> | |
| 309 | |
| 310 <div class="box legend"> | |
| 311 <div class="scale"> | |
| 312 <div class="color"></div> | |
| 313 <div class="flex ticks"> | |
| 314 <div><50</div> | |
| 315 <div>70</div> | |
| 316 <div>90+</div> | |
| 317 </div> | |
| 318 </div> | |
| 319 | |
| 320 <div> | |
| 321 <p class="text-center"> | |
| 322 <small> | |
| 323 Alphafold produces a | |
| 324 <a href="https://alphafold.ebi.ac.uk/faq#faq-5" target="_blank"> | |
| 325 per-residue confidence score (pLDDT) | |
| 326 </a> | |
| 327 between 0 and 100. Some regions below 50 pLDDT may be | |
| 328 unstructured in isolation. | |
| 329 </small> | |
| 330 </p> | |
| 331 </div> | |
| 332 </div> | |
| 333 </div> | |
| 334 </div> | |
| 335 | |
| 336 <div class="flex col controls"> | |
| 337 <div class="box text-center"> | |
| 338 <h3> Select model </h3> | |
| 339 <p>The top-ranked structures predicted by Alphafold</p> | |
| 340 <div> | |
| 341 <button class="btn selected" id="btn-ranked_0" onclick="setModel(0);"> | |
| 342 Ranked 0 | |
| 343 </button> | |
| 344 | |
| 345 <button class="btn" id="btn-ranked_1" onclick="setModel(1);"> | |
| 346 Ranked 1 | |
| 347 </button> | |
| 348 | |
| 349 <button class="btn" id="btn-ranked_2" onclick="setModel(2);"> | |
| 350 Ranked 2 | |
| 351 </button> | |
| 352 | |
| 353 <button class="btn" id="btn-ranked_3" onclick="setModel(3);"> | |
| 354 Ranked 3 | |
| 355 </button> | |
| 356 | |
| 357 <button class="btn" id="btn-ranked_4" onclick="setModel(4);"> | |
| 358 Ranked 4 | |
| 359 </button> | |
| 360 </div> | |
| 361 </div> | |
| 362 | |
| 363 <div class="box text-center"> | |
| 364 <h3> Toggle representations </h3> | |
| 365 <div> | |
| 366 <button class="btn selected" id="btn-cartoon" onclick="toggleModelRepresentation('cartoon');"> | |
| 367 Cartoon | |
| 368 </button> | |
| 369 | |
| 370 <button class="btn" id="btn-ball-stick" onclick="toggleModelRepresentation('ball+stick');"> | |
| 371 Ball + stick | |
| 372 </button> | |
| 373 | |
| 374 <button class="btn" id="btn-surface" onclick="toggleModelRepresentation('surface');"> | |
| 375 Surface | |
| 376 </button> | |
| 377 | |
| 378 <button class="btn" id="btn-backbone" onclick="toggleModelRepresentation('backbone');"> | |
| 379 Backbone | |
| 380 </button> | |
| 381 </div> | |
| 382 </div> | |
| 383 | |
| 384 <div class="box text-center"> | |
| 385 <h3> Actions </h3> | |
| 386 <div> | |
| 387 <button class="btn selected" id="btn-toggle-spin" onclick="toggleSpin();"> | |
| 388 Toggle spin | |
| 389 </button> | |
| 390 | |
| 391 <button class="btn" id="btn-toggle-dark" onclick="toggleDark();"> | |
| 392 Dark mode | |
| 393 </button> | |
| 394 </div> | |
| 395 </div> | |
| 396 | |
| 397 <div class="box text-center"> | |
| 398 <h3> Download </h3> | |
| 399 <div> | |
| 400 <button class="btn green" onclick="downloadPng();"> | |
| 401 Snapshot | |
| 402 </button> | |
| 403 | |
| 404 <button class="btn green" onclick="downloadPdb();"> | |
| 405 PDB | |
| 406 </button> | |
| 407 </div> | |
| 408 </div> | |
| 409 </div> | |
| 410 </div> | |
| 411 </body> | |
| 412 | |
| 413 | |
| 414 <script type="text/javascript"> | |
| 415 | |
| 416 // Render NGLviewer for PDB files | |
| 417 | |
| 418 // State management has been implemented with vanilla Js but could have used | |
| 419 // Vue - it's a fairly simple use case so a global 'state' object works fine | |
| 420 // without complicating things too much. | |
| 421 | |
| 422 | |
| 423 // Define a custom color scheme to represent model confidence consistently | |
| 424 // across different representations | |
| 425 // ------------------------------------------------------------------------ | |
| 426 const colorScale = chroma.scale([ | |
| 427 'red', 'yellow', 'green', 'cyan', 'blue' | |
| 428 ]).mode('lab').domain([0, 0.9]); | |
| 429 | |
| 430 const confidenceScheme = NGL.ColormakerRegistry.addScheme(function (params) { | |
| 431 this.atomColor = function (atom) { | |
| 432 // Actually model confidence (pLDDT) | |
| 433 const c = atom.bfactor; | |
| 434 const BREAK_RED = 40; // Below this is just plain red | |
| 435 let range, r, g, b; | |
| 436 | |
| 437 if (c < BREAK_RED) { | |
| 438 return 0xFF0000; | |
| 439 } | |
| 440 const p = (c - BREAK_RED) / (100 - BREAK_RED) | |
| 441 return eval(colorScale(p).hex().replace('#', '0x')); | |
| 442 }; | |
| 443 }); | |
| 444 | |
| 445 // NGL color schemes https://nglviewer.org/ngl/api/manual/usage/coloring.html | |
| 446 const COLORSCHEME = confidenceScheme; //'bfactor' | |
| 447 | |
| 448 const MODELS = [ | |
| 449 'ranked_0.pdb', | |
| 450 'ranked_1.pdb', | |
| 451 'ranked_2.pdb', | |
| 452 'ranked_3.pdb', | |
| 453 'ranked_4.pdb', | |
| 454 ] | |
| 455 | |
| 456 const REPRESENTATIONS = [ | |
| 457 'cartoon', | |
| 458 'ball+stick', | |
| 459 'surface', | |
| 460 'backbone', | |
| 461 ] | |
| 462 | |
| 463 const DEFAULT_REPRESENTATION = REPRESENTATIONS[0]; | |
| 464 const MAX_CLICK_INTERVAL_MS = 500; // For debouncing model clicks | |
| 465 | |
| 466 let stage; | |
| 467 let nonceSetModel; | |
| 468 | |
| 469 let state = { | |
| 470 model: 0, | |
| 471 modelObject: null, | |
| 472 representations: {}, | |
| 473 colorScheme: 'bfactor', | |
| 474 darkMode: false, | |
| 475 loading: 1, | |
| 476 spin: true, | |
| 477 } | |
| 478 | |
| 479 const uri = (i) => MODELS[i]; | |
| 480 // Switch to this function to return sample model URI (local dev) | |
| 481 // const uri = (i) => `https://raw.githubusercontent.com/neoformit/alphafold-galaxy/main/data/${MODELS[i]}`; | |
| 482 | |
| 483 document.addEventListener("DOMContentLoaded", async function () { | |
| 484 // Can set debug for development if NGL is being... funny | |
| 485 // NGL.setDebug(true) | |
| 486 | |
| 487 // Create NGL Stage object | |
| 488 stage = new NGL.Stage("ngl-root", { backgroundColor: 'white' }); | |
| 489 | |
| 490 // Handle window resizing | |
| 491 window.addEventListener("resize", () => stage.handleResize()); | |
| 492 | |
| 493 loadModel(); | |
| 494 while (true) { | |
| 495 if (!state.loading) { | |
| 496 // Reload page if NGL failed to display. Weird occassional bug. | |
| 497 const canvas = document.querySelector('#ngl-root canvas'); | |
| 498 canvas.height < 50 && window.reload(); | |
| 499 break | |
| 500 } | |
| 501 await new Promise(resolve => setTimeout(resolve, 500)); | |
| 502 } | |
| 503 }); | |
| 504 | |
| 505 // Models ------------------------------------------------------------------ | |
| 506 | |
| 507 const setModel = (ix) => { | |
| 508 state.model = ix; | |
| 509 stage.removeComponent(state.modelObject); | |
| 510 setLoading(1); | |
| 511 | |
| 512 // Debounce rapid model clicking with a nonce | |
| 513 nonceSetModel = new Object(); | |
| 514 const localNonce = nonceSetModel; | |
| 515 setTimeout( () => { | |
| 516 if (localNonce === nonceSetModel) { | |
| 517 // The user has stopped clicking, hurray... | |
| 518 loadModel().then(updateButtons); | |
| 519 } | |
| 520 }, MAX_CLICK_INTERVAL_MS); | |
| 521 } | |
| 522 | |
| 523 const loadModel = () => { | |
| 524 reps = Object.keys(state.representations); | |
| 525 if (reps.length) { | |
| 526 state.representations = {}; | |
| 527 } else { | |
| 528 reps = [DEFAULT_REPRESENTATION]; | |
| 529 } | |
| 530 | |
| 531 // Load PDB entry | |
| 532 return stage.loadFile(uri(state.model)).then( (o) => { | |
| 533 state.modelObject = o; | |
| 534 reps.forEach( (r) => addModelRepresentation(r) ); | |
| 535 stage.setSpin(state.spin); | |
| 536 o.autoView(); | |
| 537 setLoading(0); | |
| 538 }) | |
| 539 } | |
| 540 | |
| 541 // Representations --------------------------------------------------------- | |
| 542 | |
| 543 const toggleModelRepresentation = (rep) => { | |
| 544 rep in state.representations ? | |
| 545 removeModelRepresentation(rep) | |
| 546 : addModelRepresentation(rep) | |
| 547 } | |
| 548 | |
| 549 const addModelRepresentation = (rep) => { | |
| 550 state.representations[rep] = | |
| 551 state.modelObject.addRepresentation(rep, {colorScheme: COLORSCHEME}); | |
| 552 updateButtons(); | |
| 553 } | |
| 554 | |
| 555 const removeModelRepresentation = (rep) => { | |
| 556 o = state.representations[rep]; | |
| 557 state.modelObject.removeRepresentation(o); | |
| 558 delete state.representations[rep]; | |
| 559 updateButtons(); | |
| 560 } | |
| 561 | |
| 562 const clearModelRepresentations = () => { | |
| 563 state.modelObject && state.modelObject.removeAllRepresentations(); | |
| 564 state.representations = {}; | |
| 565 } | |
| 566 | |
| 567 // Actions ----------------------------------------------------------------- | |
| 568 | |
| 569 const toggleDark = () => { | |
| 570 state.darkMode = !state.darkMode; | |
| 571 stage.setParameters({ | |
| 572 backgroundColor: state.darkMode ? 'black' : 'white', | |
| 573 }); | |
| 574 const btn = document.querySelector('#btn-toggle-dark'); | |
| 575 btn && btn.classList.toggle('selected'); | |
| 576 } | |
| 577 | |
| 578 const setLoading = (state) => { | |
| 579 document.getElementById('ngl-loading') | |
| 580 .style.display = state ? 'flex' : 'none'; | |
| 581 state.loading = state; | |
| 582 } | |
| 583 | |
| 584 const toggleSpin = () => { | |
| 585 stage.toggleSpin(); | |
| 586 const btn = document.querySelector('#btn-toggle-spin'); | |
| 587 btn && btn.classList.toggle('selected'); | |
| 588 state.spin = !state.spin; | |
| 589 } | |
| 590 | |
| 591 const downloadPng = () => { | |
| 592 const params = { | |
| 593 factor: 3, | |
| 594 antialias: true, | |
| 595 } | |
| 596 stage.makeImage(params).then( (blob) => { | |
| 597 const name = MODELS[state.model].replace('.pdb', '.png'); | |
| 598 const url = URL.createObjectURL(blob); | |
| 599 makeDownload(url, name); | |
| 600 }) | |
| 601 } | |
| 602 | |
| 603 const downloadPdb = () => { | |
| 604 const url = uri(state.model); | |
| 605 const name = `alphafold_${MODELS[state.model]}`; | |
| 606 makeDownload(url, name); | |
| 607 } | |
| 608 | |
| 609 const makeDownload = (url, name) => { | |
| 610 // Will not work with cross-origin urls (i.e. during development) | |
| 611 console.log(`Creating file download for ${name}, href ${url}`); | |
| 612 const saveLink = document.createElement('a'); | |
| 613 saveLink.href = url; | |
| 614 saveLink.download = name; | |
| 615 document.body.appendChild(saveLink); | |
| 616 saveLink.dispatchEvent( | |
| 617 new MouseEvent('click', { | |
| 618 bubbles: true, | |
| 619 cancelable: true, | |
| 620 view: window | |
| 621 }) | |
| 622 ); | |
| 623 document.body.removeChild(saveLink); | |
| 624 } | |
| 625 | |
| 626 const updateButtons = () => { | |
| 627 MODELS.forEach( (name, i) => { | |
| 628 const id = `#btn-${name.replace('.pdb', '')}`; | |
| 629 const btn = document.querySelector(id); | |
| 630 if (!btn) return | |
| 631 i == state.model ? | |
| 632 btn.classList.add('selected') | |
| 633 : btn.classList.remove('selected'); | |
| 634 }) | |
| 635 | |
| 636 REPRESENTATIONS.forEach( (name) => { | |
| 637 const id = `#btn-${name}`.replace('+', '-'); | |
| 638 const btn = document.querySelector(id); | |
| 639 if (!btn) return | |
| 640 if (name in state.representations) { | |
| 641 btn.classList.add('selected') | |
| 642 } else { | |
| 643 btn.classList.remove('selected'); | |
| 644 } | |
| 645 }); | |
| 646 | |
| 647 // Show "Nothing to display" if no representations are selected | |
| 648 document.querySelector('#ngl-nothing').style.display = | |
| 649 Object.keys(state.representations).length ? | |
| 650 'none' | |
| 651 : 'block'; | |
| 652 } | |
| 653 | |
| 654 </script> | |
| 655 | |
| 656 </html> |
