Compare commits

...

2 Commits

Author SHA1 Message Date
dsehnal
14e619d6d2 experimental sequence theme 2025-08-28 06:26:52 +02:00
Victoria Doshchenko
42d969bbeb MVS: example story improvements (#1632)
* add intro scene

* fixes

* add author name
2025-08-26 18:55:34 +02:00
4 changed files with 142 additions and 69 deletions

View File

@@ -122,7 +122,8 @@
"Lukáš Polák <admin@lukaspolak.cz>",
"Chetan Mishra <chetan.s115@gmail.com>",
"Zach Charlop-Powers <zach.charlop.powers@gmail.com>",
"Kim Juho <juho_kim@outlook.com>"
"Kim Juho <juho_kim@outlook.com>",
"Victoria Doshchenko <doshchenko.victoria@gmail.com>"
],
"license": "MIT",
"devDependencies": {
@@ -203,4 +204,4 @@
"optional": true
}
}
}
}

View File

@@ -3,6 +3,7 @@
*
* @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';
@@ -84,15 +85,21 @@ const _Audio4 = audioPathBase + '/examples/audio/AudioMOM1_D.mp3';
const q = (expr: string, lang = 'pymol') =>
`!query=${encodeURIComponent(expr)}&lang=${lang}&action=highlight,focus`;
const description_intro = `
# Molecule of the Month: Myoglobin
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:
[Play](${encodeURIComponent(`!play-audio=${_Audio1}`)})
[Pause](!pause-audio)
[Stop](!stop-audio)
${createAudioControls(_Audio1)}
Myoglobin was the first protein to have its atomic structure determined, revealing how it stores oxygen in muscle cells.
@@ -161,9 +168,7 @@ const charged_residues = q(formatMolScript(query3), 'mol-script');
const description_p1 = `
# Myoglobin and Whales
Basic controls for the audio comments:
[Play](${encodeURIComponent(`!play-audio=${_Audio2}`)})
[Pause](!pause-audio)
[Stop](!stop-audio)
${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
@@ -184,9 +189,7 @@ high concentrations.
const description_p2 = `
# Oxygen Bound to Myoglobin
Basic controls for the audio comments:
[Play](${encodeURIComponent(`!play-audio=${_Audio3}`)})
[Pause](!pause-audio)
[Stop](!stop-audio)
${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
@@ -202,9 +205,7 @@ appear and disappear, allowing oxygen in and out.
const description_p3 = `
# Molecule of the Month: Myoglobin
Basic controls for the audio comments:
[Play](${encodeURIComponent(`!play-audio=${_Audio1}`)})
[Pause](!pause-audio)
[Stop](!stop-audio)
${createAudioControls(_Audio4)}
The atomic structure of myoglobin revealed many of the basic principles
of protein structure and stability. For instance, the structure showed
@@ -235,10 +236,67 @@ PDB entry [2jho](https://www.rcsb.org/structure/2jho) includes myoglobin poisone
`;
const Steps = [
{
header: 'Introduction',
key: 'first-slide',
description: desc_intro,
linger_duration_ms: 0,
state: (): Root => {
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],
} satisfies MVSNodeParams<'camera'>,
},
{
header: 'Molecule of the Month: Myoglobin',
key: 'intro',
description: description_intro,
description: description_p0,
linger_duration_ms: 45000,
transition_duration_ms: 500,
state: (): Root => {
@@ -247,39 +305,10 @@ const Steps = [
builder.canvas({ custom: { molstar_postprocessing: { enable_outline: false } } });
const _1mbn = structure(builder, '1MBN');
_1mbn.component({ selector: 'ligand' })
.representation({ ref: 'ligand', type: 'ball_and_stick' })
.color({ color: 'orange' });
// FE and O should be spacefill
_1mbn.component({ selector: { auth_seq_id: 155, label_atom_id: 'F' } })
.representation({ type: 'spacefill' })
.color({ color: 'yellow' });
_1mbn.component({ selector: { auth_seq_id: 154 } })
.representation({ type: 'spacefill' })
.color({ color: 'blue' });
_1mbn.component({ selector: { auth_seq_id: 154 } })
.representation({ type: 'spacefill' })
.color({ color: 'blue' });
const chA = _1mbn.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' } });
const _1mbn = build1mbn(builder, '1MBN');
// whale
_1mbn.component({ selector: { label_asym_id: 'A' } })
_1mbn.struct.component({ selector: { label_asym_id: 'A' } })
.representation({ type: 'spacefill', custom: { molstar_representation_params: { ignoreLight: true } } })
.colorFromSource({
schema: 'all_atomic',
@@ -296,29 +325,12 @@ const Steps = [
}
}).opacity({ ref: 'cpkopa1', opacity: 0.0 });
_1mbn.component({ selector: { auth_seq_id: 155 } })
_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 });
const prims = _1mbn.primitives({
ref: 'prims',
label_opacity: 1,
label_background_color: 'grey',
custom: {
molstar_markdown_commands: {
// 'apply-snapshot': 'interlude',
'play-audio': _Audio1,
}
}
});
prims.label({
text: 'Start Comments',
position: [13.5, 45.1, 7.7],
label_size: 5
});
addNextButton(builder, 'whale', [13.5, 0, 7.7]);
addNextButton(builder, 'whale', [13.5, -4, 7.7]);
// doesnt work for first slide, but work afterward
builder.extendRootCustomState({
molstar_on_load_markdown_commands: {
'play-audio': _Audio1,
@@ -543,7 +555,7 @@ const Steps = [
start: 0.0,
end: 1.0,
});
addNextButton(builder, 'oxygen', [-18.9, 10, 7.3]);
addNextButton(builder, 'oxygen', [-18.9, -4, 7.3]);
anim.interpolate({
kind: 'scalar',
target_ref: 'next',
@@ -897,7 +909,7 @@ function addNextButton(builder: any, snapshotKey: string, position: [number, num
.label({
ref: 'next_label',
position: position,
text: 'Click me to go next',
text: 'Next Scene →',
label_color: 'white',
label_size: 5
});
@@ -955,4 +967,54 @@ export function buildStory(): MVSData_States {
timestamp: new Date().toISOString(),
}
};
}
function build1mbn(builder: any, pdbId: string) {
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: string) {
return `
[ **▶ Play** ](${encodeURIComponent(`!play-audio=${url}`)})
[ **⏸ Pause** ](!pause-audio)
[ **⏹ Stop** ](!stop-audio)
[ **Hide** ](!dispose-audio)
`;
}

View File

@@ -11,6 +11,7 @@ import { StateTransformParameters } from './state/common';
export class PluginUIContext extends PluginContext {
readonly customParamEditors = new Map<string, StateTransformParameters.Class>();
readonly customUIState: Record<string, any> = {};
private initCustomParamEditors() {
if (!this.spec.customParamEditors) return;

View File

@@ -24,6 +24,8 @@ import { elementLabel } from '../mol-theme/label';
import { Icon, HelpOutlineSvg } from './controls/icons';
import { StructureSelectionManager } from '../mol-plugin-state/manager/structure/selection';
import { arrayEqual } from '../mol-util/array';
import { Observable } from 'rxjs';
import { ThemeProvider } from '../mol-theme/theme';
const MaxDisplaySequenceLength = 5000;
// TODO: add virtualized Select controls (at best with a search box)?
@@ -244,6 +246,13 @@ export class SequenceView extends PluginUIComponent<{ defaultMode?: SequenceView
}
});
const experimentalSequenceTheme = this.plugin.customUIState.experimentalSequenceTheme as Observable<ThemeProvider<any, any> | undefined> | undefined;
if (experimentalSequenceTheme) {
this.subscribe(experimentalSequenceTheme as Observable<ThemeProvider<any, any>>, theme => {
// do stuff
});
}
const modeOptions = this.plugin.spec.components?.sequenceViewer?.modeOptions;
if (modeOptions) {
const modeSet = new Set(modeOptions);