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>&lt;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>