molstar
Version:
A comprehensive macromolecular library.
930 lines (900 loc) • 39.3 kB
JavaScript
/**
* Copyright (c) 2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Ludovic Autin <autin@scripps.edu>
* @author Victoria Doshchenko <doshchenko.victoria@gmail.com>
*/
import { decodeColor } from '../../../extensions/mvs/helpers/utils.js';
import { createMVSBuilder } from '../../../extensions/mvs/tree/mvs/mvs-builder.js';
import { Mat4 } from '../../../mol-math/linear-algebra/3d/mat4.js';
import { Vec3 } from '../../../mol-math/linear-algebra/3d/vec3.js';
import { MolScriptBuilder as MS } from '../../../mol-script/language/builder.js';
import { formatMolScript } from '../../../mol-script/language/expression-formatter.js';
// 1pmb->1mbn
const align = Mat4.fromArray(Mat4.zero(), [0.4634187130865737, -0.7131589697034304, 0.5259728687171936, 0, -0.22944227902330105, -0.6698811108214233, -0.7061273127008398, 0, 0.8559202154942049, 0.2065522332899299, -0.4740643150728161, 0, -52.54880970106205, 37.49099778180445, -6.133850309914719, 1], 0);
// 1mbo->1myf
const alignmbo = Mat4.fromArray(Mat4.zero(), [-0.8334619943964441, -0.512838061396133, -0.20576353166796402, 0, -0.20145089001561267, 0.628743285359846, -0.7510655776229758, 0, 0.5145474196737698, -0.5845332204089626, -0.6273453801378679, 0, 11.864847328611186, -1.5261713438028912, 23.638919347623467, 1], 0);
const ill_color = (color, carbonLightness) => ({
molstar_color_theme_name: 'illustrative',
molstar_color_theme_params: {
style: {
name: 'uniform',
params: {
value: decodeColor(color),
saturation: 0,
lightness: 0,
}
},
carbonLightness: carbonLightness // required parameter
}
});
const GColors2 = ill_color('#947c7c', 0.8);
/* from David Goodsell style
in his illustrate software
HETATM-H-------- 0,9999, 1.1,1.1,1.1, 0.0
HETATMH--------- 0,9999, 1.0,1.0,1.0, 0.0
ATOM -H-------- 0,9999, 1.0,1.0,1.0, 0.0
ATOM H--------- 0,9999, 1.0,1.0,1.0, 0.0
HETATM-----HOH-- 0,9999, 1.0,1.0,0.0, 0.0
ATOM -OD--ASP A 0,9999 1.00, 0.20, 0.20, 1.6
ATOM -OE--GLU A 0,9999 1.00, 0.20, 0.20, 1.6
ATOM -NZ--LYS A 0,9999 0.10, 0.70, 1.00, 1.6
ATOM -NH--ARG A 0,9999 0.10, 0.70, 1.00, 1.6
ATOM -NE--ARG A 0,9999 0.10, 0.70, 1.00, 1.6
ATOM -ND--HIS A 0,9999 0.10, 0.70, 1.00, 1.6
ATOM -NE--HIS A 0,9999 0.10, 0.70, 1.00, 1.6
ATOM -N------ A 0,9999 0.80, 0.90, 1.00, 1.5
ATOM -O------ A 0,9999 1.00, 0.80, 0.80, 1.5
ATOM -C------ A 0,9999 1.00, 1.00, 1.00, 1.6
ATOM -S------ A 0,9999 1.00, 0.90, 0.50, 1.8
HETATM-C------ - 0,9999 0.60, 0.90, 0.60, 1.5
HETATM-------- - 0,9999 0.40, 0.90, 0.40, 1.5
*/
const GColors3 = {
schema: 'all_atomic', // or maybe just 'atom'
category_name: 'atom_site',
field_name: 'type_symbol',
palette: {
kind: 'categorical',
// missing_color: ...
colors: {
'C': '#FFFFFF',
'N': '#CCE6FF',
'O': '#FFCCCC',
'S': '#FFE680',
}
}
};
const audioPathBase = 'https://raw.githubusercontent.com/molstar/molstar/master';
// For local debug
// const audioPathBase = '';
const _Audio1 = audioPathBase + '/examples/audio/AudioMOM1_A.mp3';
const _Audio2 = audioPathBase + '/examples/audio/AudioMOM1_B.mp3';
const _Audio3 = audioPathBase + '/examples/audio/AudioMOM1_C.mp3';
const _Audio4 = audioPathBase + '/examples/audio/AudioMOM1_D.mp3';
const q = (expr, lang = 'pymol') => `!query=${encodeURIComponent(expr)}&lang=${lang}&action=highlight,focus`;
const desc_intro = `
# Introduction
A story based on the orginal [first Molecule of the Month](https://pdb101.rcsb.org/motm/1) made by David Goodsell in January 2000.
🔊 *This story includes short audio commentaries to guide you through the structures.*
For the best experience, please keep your sound on or use headphones.
`;
const description_p0 = `
# Molecule of the Month: Myoglobin
Basic controls for the audio comments:
${createAudioControls(_Audio1)}
Myoglobin was the first protein to have its atomic structure determined, revealing how it stores oxygen in muscle cells.
---
## The First Protein Structure
Any discussion of protein structure must necessarily begin with **myoglobin**, because it is where the science of protein structure began. After years of arduous work, *John Kendrew* and his coworkers determined the atomic structure of myoglobin, laying the foundation for an era of biological understanding.
You can take a close look at this protein structure yourself, in **PDB entry [1mbn](https://www.rcsb.org/structure/1MBN)**. You will be amazed—just like the world was in 1960—at the beautiful intricacy of this protein.
---
## Myoglobin and Muscles
[Myoglobin](!query%3Dchain%20A%26lang%3Dpymol%26action%3Dhighlight%2Cfocus) is a **small, bright red protein**. It is very common in muscle cells and gives meat much of its red color. Its job is to **store oxygen**, for use when muscles are hard at work.
To do this, it uses a special chemical tool to capture slippery oxygen molecules: a **[heme group](!query%3Dresn%20HEM%26lang%3Dpymol%26action%3Dhighlight%2Cfocus)**. Heme is a disk-shaped molecule with a hole in the center that is perfect for holding an iron ion. The iron then forms a strong interaction with the **[oxygen molecule](!query%3Dresn%20OH%26lang%3Dpymol%26action%3Dhighlight%2Cfocus)**. As you can see in the structure, the heme group is held tightly in a deep pocket on one side of the protein.
---
## Visualizing Protein Structure
When the structure of myoglobin was solved, it posed a great challenge. The structure is so complex that **new methods** needed to be developed to display and understand it.
- *John Kendrew* used a huge wire model to build the structure based on the experimental electron density.
- Then, the artist *Irving Geis* was employed to create a picture of myoglobin for a prominent article in *Scientific American*.
- Computer graphics were still many years in the future, so he created this illustration entirely by hand—one atom at a time.
You can learn more about the work of Irving Geis at the **[Geis Archive on PDB-101](https://pdb101.rcsb.org/learn/GeisArchive)**.

*Illustration of myoglobin by Irving Geis. You can learn more about this painting at the Geis Archive on PDB-101.
Used with permission from the Howard Hughes Medical Institute, Copyright 2015.*
`;
const query1 = MS.struct.generator.atomGroups({
'entity-test': MS.core.rel.eq([
MS.struct.atomProperty.core.modelEntryId(),
'1MBN'
])
});
const firstEntity1 = q(formatMolScript(query1), 'mol-script');
const query2 = MS.struct.generator.atomGroups({
'entity-test': MS.core.rel.eq([
MS.struct.atomProperty.core.modelEntryId(),
'1PMB'
])
});
const firstEntity2 = q(formatMolScript(query2), 'mol-script');
const query3 = MS.struct.generator.atomGroups({
'entity-test': MS.core.rel.eq([
MS.struct.atomProperty.core.modelEntryId(),
'1MBN'
]),
'residue-test': MS.core.set.has([
MS.set(12, 140, 87),
MS.struct.atomProperty.macromolecular.auth_seq_id()
])
});
const charged_residues = q(formatMolScript(query3), 'mol-script');
const description_p1 = `
# Myoglobin and Whales
Basic controls for the audio comments:
${createAudioControls(_Audio2)}
If you look at John Kendrew's PDB file, you'll notice that the myoglobin he used was taken
from sperm whale muscles. Whales and dolphin have a great need for myoglobin, so that they can
store extra oxygen for use in their deep undersea dives. Typically, they have about 30
times more than in animals that live on land. A recent study revealed that a few special
modifications are needed to make this possible.
Comparing [whale myoglobin](${firstEntity1})
(PDB entry [1mbn](https://www.rcsb.org/structure/1mbn)) with
[pig myoglobin](${firstEntity2})
(PDB entry [1pmb](https://www.rcsb.org/structure/1pmb)), we find that there are
several mutations that add [extra positively-charged
amino acids](${charged_residues}) to the surface. Marine animals typically have these extra charges on
the surface of their myoglobin
to help repel neighboring molecules and prevent aggregation when myoglobin is at
high concentrations.
`;
const description_p2 = `
# Oxygen Bound to Myoglobin
Basic controls for the audio comments:
${createAudioControls(_Audio3)}
A later structure of myoglobin, PDB entry [1mbo](https://www.rcsb.org/structure/1mbo),
shows that [oxygen](${q('index 1276+1277')}) binds to
the [iron](${q('index 1275')}) atom deep inside the protein.
So how does it get in and out? The
answer is that the structure in the PDB is only one snapshot of the
protein, caught when it is in a tightly-closed form. In reality,
myoglobin (and all other proteins) is constantly in motion, performing
small flexing and breathing motions (illustrated here by PDB entry [1myf](https://www.rcsb.org/structure/1myf)). So, temporary openings constantly
appear and disappear, allowing oxygen in and out.
`;
const description_p3 = `
# Molecule of the Month: Myoglobin
Basic controls for the audio comments:
${createAudioControls(_Audio4)}
The atomic structure of myoglobin revealed many of the basic principles
of protein structure and stability. For instance, the structure showed
that when the protein chain folds into a globular structure,
[carbon-rich amino acids](${q('resn ALA+VAL+LEU+ILE+MET+PHE+TRP+PRO')}) are sheltered
inside and charged amino acids [positively](${q('resn LYS+ARG+HIS')})
and [negatively](${q('resn GLU+ASP')}) are most often found on the surface,
occasionally forming [salt bridges](${q('chain A and resi 44+47+77+18')})
that pair two opposite charges (shown here with circles).
To explore some of these principles, eplxore freely in the interactive view.
# Topics for Further Discussion
You can use the sequence comparison tool to align the sequences of different
myoglobins, looking for mutations. For instance, [here is the alignment of whale and pig myoglobin](https://www.rcsb.org/alignment?request-body=eJyljrsOwjAMRf%2FFcxgqxNKN7kXsqKpC4pZIeZTEVamq%2FDtOGRAzm%2BPje3I3eM4YV6g3CBOZ4FMZI9IcfZ%2BQoVfYa0kS6kHahFmACp7wReXQBY1QwyRNXExCEOCQHkEX5qUrjNxBWjN64GSiOCtWI%2F9y2wA9xbU3fA1V21w4ndCiKjWKQKbVfeiZ0R3HUmhfVIKz%2Bvs8HXMWv75r2%2Fzn61gJg7F71y6%2FARZEZL8%3D&response-body=eJzlU11vmzAU%2FS88Q2RsjKFv7iCA7GRJQFRVVUU0uClSQjo%2BtkVV%2FvuuyUdTadMeurcJIXF97j33HN%2FLm1HVzzvj5s3o%2B6o0bgzlUaZoyaynwvUshznPll8UpVVgRUr1hAkrmWEabVd0fQv5X75OZjLMQuNgGlvVFZqq2FTreqvqbrndlQqSXouq%2BVG1CgqvMNW97HTLbmsNp5qiUW2%2F6YD44Q16NP2q6%2BFoCKGm2S8HkfbkdqpFqI1addWuHpq2%2B%2B0R5QA9qfWyVd%2BGA9uE2vI9pORwMD%2FyzSa3n%2BN7NN%2FlLi8eB92NWgOl9vDwF1YdVnWpfho3yDQ2ql53L0f%2BR%2FMTtaCta4q6fd4126I7a7FNdHp%2B%2BwUd0chxCXY9xjA2LTRiNkWMONT2TDSimDi%2Bz1yHQDaAmGCbeTbxXR25rodsG%2FvHiCGGEHE9D2vukUcpdgnBlEGAEfU9RrFPdKbDqOMT2x8yLYpH1MWOQxzP9U3CRi5lBCGKoKlFR77j2Rg5iNkgVw%2Bg326LZq%2BH165257X5Xmx62EFo68I97F%2F1PsK99apeKasqYUpVtzf0QlwyXeeSuZikwUfQ9y5gNrGGRoYefw1jr5fQtSp7tdQb3w73rxH9G2xUeUa1MI3Aq2XDEHUpzOxUoKPV7rtqirXSqQjmgdDjcctO0v%2B4ZJ%2FYsQs5lOYyDaPwbi5zGed3XOQhD3IexdE8SGSykGORxrMwk6EYB4uxiKXIQh5OBE%2FDQAoRR3mWy4zLiCcQiiiOAZZiJvk8jXkmYpHMEnEvw3GShjxJYuiTLuJZFIwjHvB5xCdTwWUoxwsRJJyLexHK6H4eDdP453YjmQYnu9P8LrqyG%2BZHu9HFrhjsgs9ru9OT3ejabvbBbn5ld57LeSo%2B2E1PdqfB5Gx3rO3%2BH5t9%2BAU7Kf5z&encoded=true) used to create the illustration in this column.
PDB entry [2jho](https://www.rcsb.org/structure/2jho) includes myoglobin poisoned by cyanide. Take a look and you'll see that the cyanide blocks the binding site for oxygen.
# References
- 1mbn: J. C. Kendrew, R. E. Dickerson, B. E. Strandberg, R. G. Hart, D. R. Davies, D. C. Phillips & V. C. Shore (1960) Structure of Myoglobin. Nature 185, 422-427.
- J. C. Kendrew (1961) The three-dimensional structure of a protein molecule. Scientific American 205(6), 96-110.
- 1mbo: S. E. Phillips (1980) Structure and refinement of oxymyoglobin at 1.6 A resolution. Journal of Molecular Biology 142, 531-554.
- 1pmb: S. J. Smerdon, T. J. Oldfield, E. J. Dodson, G. G. Dodson, R. E. Hubbard & A. J. Wilkinson (1990) Determination of the crystal structure of recombinant pig myoglobin by molecular replacement and its refinement. Acta Crystallographica B, 46, 370-377.
- 1myf: Osapay K, Theriault Y, Wright PE, Case DA. Solution structure of carbonmonoxy myoglobin determined from nuclear magnetic resonance distance and chemical shift constraints. J Mol Biol. 1994;244(2):183-197. doi:10.1006/jmbi.1994.1718
- S. Mirceta, A. V. Signore, J. M. Burns, A. R. Cossins, K. L. Campbell & M. Berenbrink (2013) Evolution of mammalian diving capacity traced by myoglobin net surface charge. Science 340, 1234192.
`;
const Steps = [
{
header: 'Introduction',
key: 'first-slide',
description: desc_intro,
linger_duration_ms: 0,
state: () => {
const builder = createMVSBuilder();
const _1mbn = build1mbn(builder, '1MBN');
builder.extendRootCustomState({
molstar_on_load_markdown_commands: {
'dispose-audio': _Audio1,
}
});
const anim = builder.animation({
custom: {
molstar_trackball: {
name: 'spin',
params: { speed: -0.05 },
}
}
});
const prims = _1mbn.struct.primitives({
ref: 'start-story',
label_opacity: 0,
label_background_color: 'grey',
snapshot_key: 'intro'
});
prims.label({
text: 'Start story',
position: [13.5, -4, 7.7],
label_size: 8
});
anim.interpolate({
kind: 'scalar',
target_ref: 'start-story',
duration_ms: 1000,
start_ms: 1,
property: 'label_opacity',
start: 0.0,
end: 1.0,
});
return builder;
},
camera: {
position: [13.5, 21.1, 73.1],
target: [13.5, 21.1, 7.7],
up: [0, 1, 0],
},
},
{
header: 'Molecule of the Month: Myoglobin',
key: 'intro',
description: description_p0,
linger_duration_ms: 45000,
transition_duration_ms: 500,
state: () => {
const builder = createMVSBuilder();
// no outline here
builder.canvas({ custom: { molstar_postprocessing: { enable_outline: false } } });
const _1mbn = build1mbn(builder, '1MBN');
// whale
_1mbn.struct.component({ selector: { label_asym_id: 'A' } })
.representation({ type: 'spacefill', custom: { molstar_representation_params: { ignoreLight: true } } })
.colorFromSource({
schema: 'all_atomic',
category_name: 'atom_site',
field_name: 'type_symbol',
palette: {
kind: 'categorical',
colors: {
'C': '#FFFFFF',
'N': '#CCE6FF',
'O': '#FFCCCC',
'S': '#FFE680',
}
}
}).opacity({ ref: 'cpkopa1', opacity: 0.0 });
_1mbn.struct.component({ selector: { auth_seq_id: 155 } })
.representation({ type: 'spacefill', custom: { molstar_representation_params: { ignoreLight: true } } })
.color({ custom: GColors2 }).opacity({ ref: 'cpkopa2', opacity: 0.0 });
addNextButton(builder, 'whale', [13.5, -4, 7.7]);
builder.extendRootCustomState({
molstar_on_load_markdown_commands: {
'play-audio': _Audio1,
}
});
const anim = builder.animation({
custom: {
molstar_trackball: {
name: 'spin',
params: { speed: -0.05 },
}
}
});
anim.interpolate({
kind: 'scalar',
target_ref: 'lineopa',
duration_ms: 2000,
start_ms: 0,
property: 'opacity',
start: 0.0,
end: 1.0,
});
anim.interpolate({
kind: 'scalar',
target_ref: 'ligand',
start_ms: 22000,
duration_ms: 10000,
frequency: 6,
alternate_direction: true,
property: ['custom', 'molstar_representation_params', 'emissive'],
end: 1.0,
});
anim.interpolate({
kind: 'scalar',
target_ref: 'cpkopa1',
duration_ms: 5000,
start_ms: 40000,
property: 'opacity',
start: 0.0,
end: 1.0,
});
anim.interpolate({
kind: 'scalar',
target_ref: 'cpkopa2',
duration_ms: 5000,
start_ms: 40000,
property: 'opacity',
start: 0.0,
end: 1.0,
});
anim.interpolate({
kind: 'scalar',
target_ref: 'next',
duration_ms: 2000,
start_ms: 43000,
property: 'label_opacity',
start: 0.0,
end: 1.0,
});
return builder;
},
camera: {
position: [13.5, 21.1, 73.1],
target: [13.5, 21.1, 7.7],
up: [0, 1, 0],
},
},
{
header: 'Myoglobin and Whales',
key: 'whale',
description: description_p1,
linger_duration_ms: 41000,
transition_duration_ms: 500,
state: () => {
const builder = createMVSBuilder();
const _1mbn = structure(builder, '1mbn').transform({ ref: 'whalex', translation: [-30, 0, 0] });
// whale
_1mbn.component({ selector: { label_asym_id: 'A' } })
.representation({ type: 'spacefill', custom: { molstar_representation_params: { ignoreLight: true } } })
.colorFromSource({
schema: 'all_atomic', // or maybe just 'atom'
category_name: 'atom_site',
field_name: 'type_symbol',
palette: {
kind: 'categorical',
colors: {
'C': '#FFFFFF',
'N': '#CCE6FF',
'O': '#FFCCCC',
'S': '#FFE680',
}
}
});
_1mbn.component({ selector: { auth_seq_id: 155 } })
.representation({ type: 'spacefill', custom: { molstar_representation_params: { ignoreLight: true } } })
.color({ custom: GColors2 });
_1mbn.primitives({
ref: 'prims',
label_opacity: 1,
label_attachment: 'top-center',
label_show_tether: true,
label_tether_length: 1.0,
})
.label({
text: 'whale',
position: { label_asym_id: 'A', auth_seq_id: 8 },
label_size: 10
});
_1mbn.primitives({
ref: 'startres',
label_opacity: 0,
})
.label({
text: '★', label_offset: 4,
position: { label_asym_id: 'A', auth_seq_id: 12, atom_id: 96 }, label_size: 5
})
.label({
text: '★', label_offset: 4,
position: { label_asym_id: 'A', auth_seq_id: 140, auth_atom_id: 'NZ' }, label_size: 5
})
.label({
text: '★', label_offset: 4,
position: { label_asym_id: 'A', auth_seq_id: 87, auth_atom_id: 'NZ' }, label_size: 5
});
// the following doesnt work
const seld = _1mbn.component({
selector: [
{ label_asym_id: 'A', auth_seq_id: 12 },
{ label_asym_id: 'A', auth_seq_id: 140 },
{ label_asym_id: 'A', auth_seq_id: 87 }
]
});
seld.representation({ ref: 'scharged', type: 'surface', surface_type: 'gaussian', custom: { molstar_representation_params: { emissive: 0.0, ignoreLight: true } } })
.colorFromSource(GColors3);
// pig
const _1pmb = structure(builder, '1pmb').transform({ ref: 'pig', matrix: align });
_1pmb.component({ selector: { label_asym_id: 'A' } })
.representation({ type: 'spacefill', custom: { molstar_representation_params: { ignoreLight: true } } })
.colorFromSource(GColors3);
_1pmb.component({ selector: { label_asym_id: 'C', auth_seq_id: 154 } })
.representation({ type: 'spacefill', custom: { molstar_representation_params: { ignoreLight: true } } })
.color({ custom: GColors2 });
_1pmb.primitives({
ref: 'labelpig',
label_opacity: 1,
label_attachment: 'top-center',
label_show_tether: true,
label_tether_length: 1.0,
})
.label({
text: 'pig',
position: { label_asym_id: 'A', auth_seq_id: 8 },
label_size: 10
});
builder.extendRootCustomState({
molstar_on_load_markdown_commands: {
'play-audio': _Audio2,
}
});
const anim = builder.animation({
custom: {
molstar_trackball: {
name: 'spin',
params: { speed: -0.05 },
}
}
});
anim.interpolate({
kind: 'vec3',
target_ref: 'whalex',
duration_ms: 10000,
start_ms: 16000,
property: 'translation',
start: [-30, 0, 0],
end: [-60, 0, 0],
});
anim.interpolate({
kind: 'scalar',
target_ref: 'startres',
duration_ms: 1000,
start_ms: 20000,
property: 'label_opacity',
start: 0.0,
end: 1.0,
});
// pig appear at 18s
anim.interpolate({
kind: 'transform_matrix',
target_ref: 'pig',
duration_ms: 5000,
start_ms: 18000,
property: 'matrix',
translation_start: [-82.54880970106205, 37.49099778180445, -6.133850309914719],
translation_end: [-52.54880970106205, 37.49099778180445, -6.133850309914719],
});
anim.interpolate({
kind: 'scalar',
target_ref: 'labelpig',
duration_ms: 2000,
start_ms: 18000,
property: 'label_opacity',
start: 0.0,
end: 1.0,
});
addNextButton(builder, 'oxygen', [-18.9, -4, 7.3]);
anim.interpolate({
kind: 'scalar',
target_ref: 'next',
duration_ms: 2000,
start_ms: 38000,
property: 'label_opacity',
start: 0.0,
end: 1.0,
});
anim.interpolate({
kind: 'scalar',
target_ref: 'scharged',
start_ms: 20000,
duration_ms: 6000,
frequency: 6,
alternate_direction: true,
property: ['custom', 'molstar_representation_params', 'emissive'],
start: 0.0,
end: 1.0,
});
return builder;
},
camera: {
position: [-14.6, 116.1, 66.5],
target: [-18.9, 21.1, 7.3],
up: [-0.0, 0.5, -0.8],
},
},
{
header: 'Oxygen Bound',
key: 'oxygen',
description: description_p2,
linger_duration_ms: 18000,
transition_duration_ms: 500,
state: () => {
const builder = createMVSBuilder();
// NMR 1MYF
// 1A6N unbound
// 1A6M bound
// series 2G0R
const _1mbo = structure(builder, '1mbo')
.transform({ matrix: alignmbo });
const _1myf = builder
.download({ url: pdbUrl('1myf') })
.parse({ format: 'bcif' })
.modelStructure({ ref: '1myf' });
const red1 = '#d3a4a6';
const red2 = '#d75354';
const blue1 = '#02d1d1';
_1myf.component({ selector: { label_asym_id: 'A' } })
.transform({ translation: [0, 0, 0] })
.representation({ type: 'spacefill' })
.color({ color: red1 })
.opacity({ ref: 'spo', opacity: 1.0 });
// OXYY
// should animate in-out in loop
_1mbo.component({ selector: { label_asym_id: 'C', auth_seq_id: 155 } })
.representation({ type: 'spacefill' })
.color({
custom: {
molstar_color_theme_name: 'element-symbol',
molstar_color_theme_params: {
carbonColor: {
name: 'uniform',
params: { value: decodeColor(red2) }
},
}
}
});
_1myf.component({ selector: { label_asym_id: 'A' } })
.representation({ type: 'backbone' })
.color({ color: red1 });
_1mbo.component({ selector: { label_asym_id: 'D', auth_seq_id: 555 } })
.representation({
ref: 'oxy', type: 'spacefill', custom: {
molstar_representation_params: {
emissive: 0.0
}
}
})
.color({ color: blue1 });
_1mbo.component({ selector: { label_asym_id: 'D', auth_seq_id: 555 } })
.transform({ ref: 'oxyy', translation: [0, 0, 0] })
.representation({ type: 'spacefill' })
.color({ color: blue1 })
.opacity({ ref: 'oxop', opacity: 0.0 });
builder.extendRootCustomState({
molstar_on_load_markdown_commands: {
'play-audio': _Audio3,
}
});
const anim = builder.animation({
custom: {
molstar_trackball: {
name: 'spin',
params: { speed: -0.05 },
}
}
});
anim.interpolate({
kind: 'scalar',
target_ref: 'spo',
duration_ms: 5000,
start_ms: 0,
property: 'opacity',
start: 1.0,
end: 0.05,
});
anim.interpolate({
kind: 'scalar',
target_ref: '1myf',
start_ms: 11000,
duration_ms: 10000,
frequency: 4,
alternate_direction: true,
property: 'model_index',
discrete: true,
start: 0,
end: 11,
});
anim.interpolate({
kind: 'scalar',
target_ref: 'oxy',
start_ms: 3000,
duration_ms: 10000,
frequency: 7,
alternate_direction: true,
property: ['custom', 'molstar_representation_params', 'emissive'],
end: 1.0,
});
anim.interpolate({
kind: 'vec3',
target_ref: 'oxyy',
duration_ms: 5000,
start_ms: 16000,
property: 'translation',
frequency: 4,
alternate_direction: false,
start: [5, -5, -20],
end: [0, 0, 0],
noise_magnitude: 1,
});
anim.interpolate({
kind: 'scalar',
target_ref: 'oxop',
duration_ms: 1000,
start_ms: 15000,
property: 'opacity',
start: 0.0,
end: 1.0,
});
addNextButton(builder, 'end', [0, -25, 0.0]);
anim.interpolate({
kind: 'scalar',
target_ref: 'next',
duration_ms: 2000,
start_ms: 18000,
property: 'label_opacity',
start: 0.0,
end: 1.0,
});
return builder;
},
camera: {
position: [-2.2, 0.7, -78.5],
target: [-0.1, 0.7, 0.6],
up: [0, 1, 0],
},
},
{
header: 'Conclusion',
key: 'end',
description: description_p3,
linger_duration_ms: 20000,
transition_duration_ms: 500,
state: () => {
const builder = createMVSBuilder();
const _1mbn = structure(builder, '1mbn');
// resn ALA+VAL+LEU+ILE+MET+PHE+TRP+PRO
const carb = ['ALA', 'VAL', 'LEU', 'ILE', 'MET', 'PHE', 'TRP', 'PRO'].map(amk => ({ label_comp_id: amk }));
// resn LYS+ARG+HIS+ASP+GLU
const chargedp = ['LYS', 'ARG', 'HIS'].map(amk => ({ label_comp_id: amk }));
const chargedn = ['ASP', 'GLU'].map(amk => ({ label_comp_id: amk }));
// salt bridge
// ASP44-OD1-356-LYS47-NZ-388
// LYS77-NZ-613-GLU18-OE1-149
// use primitve distance_measurement
// and ellipse or ellipsoid with transparancy
_1mbn.primitives({ ref: 'dist', label_opacity: 0.0 })
.distance({
start: { label_asym_id: 'A', auth_seq_id: 44, atom_id: 356 },
end: { label_asym_id: 'A', auth_seq_id: 47, atom_id: 388 },
radius: 0.1, dash_length: 0.1,
label_size: 2
})
.distance({
start: { label_asym_id: 'A', auth_seq_id: 77, atom_id: 613 },
end: { label_asym_id: 'A', auth_seq_id: 18, atom_id: 149 },
radius: 0.1, dash_length: 0.1,
label_size: 2
});
// 44 OD1 22.300 33.300 -6.200
// 47 NZ 23.200 32.000 -8.400
const r44 = Vec3.create(22.300, 33.300, -6.200);
const r47 = Vec3.create(23.200, 32.000, -8.400);
getEllipse(builder, r44, r47, 'salt1');
// 18 OE1 16.600 22.500 20.500
// 77 NZ 14.100 23.600 22.200
const r18 = Vec3.create(16.600, 22.500, 20.500);
const r77 = Vec3.create(14.100, 23.600, 22.200);
getEllipse(builder, r18, r77, 'salt2');
const a = _1mbn.component({ selector: carb });
a.representation({ type: 'ball_and_stick' })
.color({ color: '#bec0f2' })
.opacity({ ref: 'carb', opacity: 1.0 });
const b = _1mbn.component({ selector: chargedp });
b.representation({ type: 'ball_and_stick' })
.color({ custom: ill_color('blue', 3.0) })
.opacity({ ref: 'chargedp', opacity: 1.0 });
const c = _1mbn.component({ selector: chargedn });
c.representation({ type: 'ball_and_stick' })
.color({ custom: ill_color('red', 3.0) })
.opacity({ ref: 'chargedn', opacity: 1.0 });
_1mbn.component({ selector: { label_asym_id: 'A' } })
.representation({ type: 'backbone' })
.color({ color: '#919191' });
_1mbn.component({ selector: 'ligand' })
.representation({
ref: 'ligand', type: 'ball_and_stick',
custom: {
molstar_representation_params: {
emissive: 0.0
}
}
})
.color({ color: 'orange' });
builder.extendRootCustomState({
molstar_on_load_markdown_commands: {
'play-audio': _Audio4,
}
});
const anim = builder.animation({});
anim.interpolate({
kind: 'scalar',
target_ref: 'carb',
duration_ms: 2000,
start_ms: 8000,
frequency: 2,
alternate_direction: true,
property: 'opacity',
start: 0.0,
end: 1.0,
});
anim.interpolate({
kind: 'scalar',
target_ref: 'chargedp',
duration_ms: 1000,
start_ms: 10000,
property: 'opacity',
start: 0.0,
end: 1.0,
});
anim.interpolate({
kind: 'scalar',
target_ref: 'chargedn',
duration_ms: 1000,
start_ms: 10000,
property: 'opacity',
start: 0.0,
end: 1.0,
});
// show salt bridge
anim.interpolate({
kind: 'scalar',
target_ref: 'salt1',
duration_ms: 1000,
start_ms: 11000,
property: 'opacity',
start: 0.0,
end: 0.3,
});
anim.interpolate({
kind: 'scalar',
target_ref: 'salt2',
duration_ms: 1000,
start_ms: 11000,
property: 'opacity',
start: 0.0,
end: 0.3,
});
anim.interpolate({
kind: 'scalar',
target_ref: 'dist',
duration_ms: 1000,
start_ms: 11000,
property: 'label_opacity',
start: 0.0,
end: 1.0,
});
addNextButton(builder, 'intro', [13.5, -10.0, 7.7]);
anim.interpolate({
kind: 'scalar',
target_ref: 'next',
duration_ms: 2000,
start_ms: 20000,
property: 'label_opacity',
start: 0.0,
end: 1.0,
});
return builder;
},
camera: {
position: [16.0, 47.2, 67.8],
target: [13.6, 21.1, 7.6],
up: [0.1, 0.9, -0.4],
},
},
];
function addNextButton(builder, snapshotKey, position) {
builder.primitives({
ref: 'next',
tooltip: 'Click for next part',
label_opacity: 0,
label_background_color: 'grey',
snapshot_key: snapshotKey
})
.label({
ref: 'next_label',
position: position,
text: 'Next Scene →',
label_color: 'white',
label_size: 5
});
}
function structure(builder, id) {
return builder
.download({ url: pdbUrl(id) })
.parse({ format: 'bcif' })
.modelStructure();
}
function getEllipse(builder, pos1, pos2, ref) {
const center = Vec3.add(Vec3(), pos1, pos2);
Vec3.scale(center, center, 0.5);
const major_axis = Vec3.sub(Vec3(), pos2, pos1);
const z_axis = Vec3.create(0, 0, 1);
// cross to get minor
const minor_axis = Vec3.cross(Vec3(), major_axis, z_axis);
return builder.primitives({ ref: ref, opacity: 0.33 }).ellipsoid({
center: center,
major_axis: major_axis,
minor_axis: minor_axis,
radius: [5.0, 3.0, 3.0],
color: '#cccccc',
});
}
function pdbUrl(id) {
return `https://www.ebi.ac.uk/pdbe/entry-files/download/${id.toLowerCase()}.bcif`;
}
export function buildStory() {
const snapshots = Steps.map((s, i) => {
var _a, _b;
const builder = s.state();
if (s.camera)
builder.camera(s.camera);
const description = i > 0 ? `${s.description}\n\n[Go to start](#intro)` : s.description;
return builder.getSnapshot({
title: s.header,
key: s.key,
description,
description_format: 'markdown',
linger_duration_ms: (_a = s.linger_duration_ms) !== null && _a !== void 0 ? _a : 500,
transition_duration_ms: (_b = s.transition_duration_ms) !== null && _b !== void 0 ? _b : 1000,
});
});
return {
kind: 'multiple',
snapshots,
metadata: {
title: 'RCSB PDB Molecule of the Month 1',
version: '1.0',
timestamp: new Date().toISOString(),
}
};
}
function build1mbn(builder, pdbId) {
const struct = structure(builder, '1MBN');
struct.component({ selector: 'ligand' })
.representation({ ref: 'ligand', type: 'ball_and_stick' })
.color({ color: 'orange' });
// FE and O should be spacefill
struct.component({ selector: { auth_seq_id: 155, label_atom_id: 'FE' } })
.representation({ type: 'spacefill' })
.color({ color: 'yellow' });
struct.component({ selector: { auth_seq_id: 154 } })
.representation({ type: 'spacefill' })
.color({ color: 'blue' });
struct.component({ selector: { auth_seq_id: 154 } })
.representation({ type: 'spacefill' })
.color({ color: 'blue' });
const chA = struct.component({ selector: { label_asym_id: 'A' } });
chA.representation({ type: 'surface', surface_type: 'gaussian' })
.color({ color: '#ff0303' })
.opacity({ ref: 'surfopa', opacity: 0.0 });
chA.representation({ type: 'line' })
.color({ custom: { molstar_color_theme_name: 'element-symbol' } })
.opacity({ ref: 'lineopa', opacity: 0.0 });
chA.representation({ type: 'cartoon' })
.color({ custom: { molstar_color_theme_name: 'secondary-structure' } });
return {
struct,
refs: {
surfaceOpacity: 'surfopa',
lineOpacity: 'lineopa',
}
};
}
function createAudioControls(url) {
return `
[‹ **▶ Play** ›](${encodeURIComponent(`!play-audio=${url}`)})
[‹ **⏸ Pause** ›](!pause-audio)
[‹ **⏹ Stop** ›](!stop-audio)
[‹ **Hide** ›](!dispose-audio)
`;
}