BINANAInterface.ts 11.7 KB
Newer Older
jdurrant's avatar
jdurrant committed
1
2
3
4
5
// This file is part of BINANA, released under the Apache 2.0 License. See
// LICENSE.md or go to https://opensource.org/licenses/Apache-2.0 for full
// details. Copyright 2020 Jacob D. Durrant.

import * as ThreeDMol from "./UI/ThreeDMol";
jdurrant's avatar
jdurrant committed
6
import * as Store from "./Vue/Store";
jdurrant's avatar
jdurrant committed
7

jdurrant's avatar
jdurrant committed
8
9
declare var jQuery;

jdurrant's avatar
jdurrant committed
10
11
let viewer;
let receptorMol;
jdurrant's avatar
jdurrant committed
12
let ligandMol;
jdurrant's avatar
jdurrant committed
13
14
let binanaData;

jdurrant's avatar
jdurrant committed
15
16
// A single atom may participate in multiple interactions with other atoms.
// Make sure each atom is rendered in the viewer only once.
jdurrant's avatar
jdurrant committed
17
18
19
20
21
22
23
24
25
26
27
let idxOfAtomsSeen;

/**
 * Sets up the interface, brining several variables into the module's scope.
 * @param  {*} view   The 3Dmoljs viewer.
 * @param  {*} recep  The 3Dmoljs receptor molecule object.
 * @param  {*} lig    The 3Dmoljs ligand molecule object.
 */
export function setup(view: any, recep: any, lig: any) {
    viewer = view;
    receptorMol = recep;
jdurrant's avatar
jdurrant committed
28
    ligandMol = lig;
jdurrant's avatar
jdurrant committed
29
30
31
32
33
34
35
36
37
}

/**
 * Starts BINANA.
 * @param  {string} pdbtxt  The text of the receptor.
 * @param  {string} ligtxt  The text of the ligand.
 * @returns void
 */
export function start(pdbtxt: string, ligtxt: string): void {
jdurrant's avatar
jdurrant committed
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
    let binanaParams = Store.store.state["binanaParams"];

    jQuery("body").addClass("waiting");

    setTimeout(() => {
        var myWorker = new Worker('binanaWebWorker.js', { type: "module" });
        myWorker.postMessage([pdbtxt, ligtxt, binanaParams]);

        myWorker.onmessage = function(e) {
            binanaData = e.data;

            // Update the store too.
            Store.store.commit("setVar", {
                name: "jsonOutput",
                val: JSON.stringify(binanaData, undefined, 1)
            });

            jQuery("body").removeClass("waiting");
        }
    }, 250);
jdurrant's avatar
jdurrant committed
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
}

/**
 * Highlights the specified interaction in the viewer. Sets the atoms involved
 * in the interaction to a different color.
 * @param  {string} interactionName  The name of the interaction.
 * @returns void
 */
export function highlight(interactionName: string): void {
    clearInteraction();

    // make an array for the interactions
    let interactionType = binanaData[interactionName];

    // A single atom may participate in multiple interactions with other
    // atoms. Make sure each atom is rendered in the viewer only once.
    idxOfAtomsSeen = new Set([]);
    let ligAtomInfs = [];
    let recAtomInfs = [];

jdurrant's avatar
jdurrant committed
78
    let colorMsg = Store.defaultColorMsg;
jdurrant's avatar
jdurrant committed
79
80
81
82
83
84
85
86
87
88
89

    // loop through the interactions
    for (let i = 0; i < interactionType.length; i++) {
        let ligandAtomInfs = interactionType[i]["ligandAtoms"];
        let receptorAtomInfs = interactionType[i]["receptorAtoms"];

        // Start by assuming color by molecule.
        let ligColor = "yellow";
        let recepColor = "red";
        colorMsg = "Ligand atoms are highlighted in yellow, and receptor atoms are highlighted in red.";

jdurrant's avatar
jdurrant committed
90
        if (Store.store["state"]["colorByInteraction"] === Store.InteractionColoring.INTERACTION) {
jdurrant's avatar
jdurrant committed
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
            // Instead color by interaction.
            switch (interactionName) {
                case "hydrogenBonds":
                    if (ligandAtomInfs.length == 2) {
                        ligColor = "yellow";
                        recepColor = "red";
                    } else {
                        ligColor = "red";
                        recepColor = "yellow";
                    }
                    colorMsg = "Hydrogen-bond donors are highlighted in yellow, and hydrogen-bond acceptors are highlighted in red.";
                    break;
                case "saltBridges":
                    if (["LYS", "ARG", "HIS", "ARN", "HIP"].indexOf(receptorAtomInfs[0]["resName"]) !== -1) {
                        // Protein residue is positive.
                        recepColor = "blue";
                        ligColor = "red";
                    } else {
                        // Protein residue is negative.
                        recepColor = "red";
                        ligColor = "blue";
                    }
                    colorMsg = "Positively charged groups are highlighted in blue, and negatively charged groups are highlighted in red.";
                    break;
            }
        }

jdurrant's avatar
jdurrant committed
118
        Store.store.commit("setVar", {
jdurrant's avatar
jdurrant committed
119
120
121
122
            name: "colorMessage",
            val: colorMsg
        });

jdurrant's avatar
jdurrant committed
123
124
125
126
127
128
        ligAtomInfs = ligAtomInfs.concat(
            getAtomObjRadiusColor(ligandMol, ligandAtomInfs, ligColor)
        );
        recAtomInfs = recAtomInfs.concat(
            getAtomObjRadiusColor(receptorMol, receptorAtomInfs, recepColor)
        );
jdurrant's avatar
jdurrant committed
129
130
131
    }

    // Add spheres
jdurrant's avatar
jdurrant committed
132
133
134
135
136
137
138
    if (Store.store["state"]["colorByInteraction"] !== Store.InteractionColoring.NONE) {
        drawSpheres(ligAtomInfs.concat(recAtomInfs));
    }

    if (Store.store["state"]["bondVisible"]) {
        drawCylinders(interactionType, interactionName);
    }
jdurrant's avatar
jdurrant committed
139
140
141

    // Render sticks of protein model too.
    ThreeDMol.showSticksAsAppropriate();
jdurrant's avatar
jdurrant committed
142

jdurrant's avatar
jdurrant committed
143
144
145
146
147
148
149
150
151
152
    receptorMol["setStyle"](
        {
            "index": recAtomInfs.map(
                i => i[0]["index"]
            ),
            "byres": true
        },
        {
            "stick": { "radius": 0.1 },  // 0.15
            "cartoon": { "color": 'spectrum' },
jdurrant's avatar
jdurrant committed
153
154
        },
        true  // add
jdurrant's avatar
jdurrant committed
155
156
    );

jdurrant's avatar
jdurrant committed
157
158
159
160
    // Show protein ribbon again (otherwise sometimes parts of ribbon
    // disappear).
    ThreeDMol.showProteinRibbon(true);

jdurrant's avatar
jdurrant committed
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
    viewer["render"]();
}

/**
 * Draws interaction spheres in the 3Dmoljs viewer.
 * @param  {*} atomInfs  Information about the atoms.
 * @returns void
 */
function drawSpheres(atomInfs: any[]): void {
    const coorsLen = atomInfs.length;
    for (let i = 0; i < coorsLen; i++) {
        const atomInf = atomInfs[i];
        const atom = atomInf[0];
        viewer["addSphere"]({
            "center": {"x": atom["x"], "y": atom["y"], "z": atom["z"]},
            "radius": 0.6 * atomInf[1],   // scale down vdw radius a bit.
            "color": atomInf[2],
jdurrant's avatar
jdurrant committed
178
            "opacity": 0.65
jdurrant's avatar
jdurrant committed
179
180
181
182
        });
    }
}

jdurrant's avatar
jdurrant committed
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
/**
 * Draws the cylinders representing the interactions.
 * @param  {*}      interactionType  The atom informations that match this
 *                                   interaction type.
 * @param  {string} interactionName  The name of the interaction.
 * @returns void
 */
function drawCylinders(interactionType: any[], interactionName: string): void {
    // Get atoms.
    let interactionTypeAtoms = interactionType.map(i => [
        i["ligandAtoms"].map(l => atomInfTo3DMolAtom(ligandMol, l)),
        i["receptorAtoms"].map(r => atomInfTo3DMolAtom(receptorMol, r))
    ]);

    switch (interactionName) {
        case "hydrogenBonds":
            let hBondHeavyAtomPairs = interactionTypeAtoms.map(i => [
                [2 - i[0].length, i[0].filter(a => a["elem"] !== "H")[0]],
                [2 - i[1].length, i[1].filter(a => a["elem"] !== "H")[0]]
            ]);
            hBondHeavyAtomPairs = hBondHeavyAtomPairs.map(i => i.sort().map(i2 => i2[1]));

            for (let i = 0; i < hBondHeavyAtomPairs.length; i++){
                // viewer["addCylinder"]({
                viewer["addArrow"]({
                    "dashed": true,
                    "start": {"x": hBondHeavyAtomPairs[i][0]["x"], "y": hBondHeavyAtomPairs[i][0]["y"], "z": hBondHeavyAtomPairs[i][0]["z"]},
                    "end": {"x": hBondHeavyAtomPairs[i][1]["x"], "y": hBondHeavyAtomPairs[i][1]["y"], "z": hBondHeavyAtomPairs[i][1]["z"]},
                    "radius": 0.1,
                    "radiusRatio": 3.0,
                    "mid": 0.7,
                    "fromCap": 2,
                    "toCap": 2,
                    "color": 'black'
                });
            }
            break;
        default:
            // If not hydrogen bond, just line between geometric centers.
            let centerPoints = interactionTypeAtoms.map(i => [
                [i[0].length, i[0].map(a => [a["x"], a["y"], a["z"]]).reduce(
                    (c1, c2) => [(c1[0] + c2[0]), (c1[1] + c2[1]), (c1[2] + c2[2])]
                )],
                [i[1].length, i[1].map(a => [a["x"], a["y"], a["z"]]).reduce(
                    (c1, c2) => [(c1[0] + c2[0]), (c1[1] + c2[1]), (c1[2] + c2[2])]
                )],
            ]);
            centerPoints = centerPoints.map(i => [
                i[0][1].map(v => v / i[0][0]),
                i[1][1].map(v => v / i[1][0]),
            ]);

            for (let i = 0; i < centerPoints.length; i++) {
                let start = {"x": centerPoints[i][0][0], "y": centerPoints[i][0][1], "z": centerPoints[i][0][2]};
                let end = {"x": centerPoints[i][1][0], "y": centerPoints[i][1][1], "z": centerPoints[i][1][2]};

                viewer["addCylinder"]({
                // viewer["addArrow"]({
                    "dashed": true,
                    "start": start,
                    "end": end,
                    "radius": 0.1,
                    // "radiusRatio": 3.0,
                    // "mid": 0.7,
                    "fromCap": 2,
                    "toCap": 2,
                    "color": 'black'
                });
            }
            break;
    }
}

jdurrant's avatar
jdurrant committed
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
// See https://en.wikipedia.org/wiki/Atomic_radii_of_the_elements_(data_page)
let vdwRadii = {
    "H": 1.20,
    "He": 1.40,
    "Li": 1.82,
    "Be": 1.53,
    "B": 1.92,
    "C": 1.70,
    "N": 1.55,
    "O": 1.52,
    "F": 1.47,
    "Ne": 1.54,
    "Na": 2.27,
    "Mg": 1.73,
    "Al": 1.84,
    "Si": 2.10,
    "P": 1.80,
    "S": 1.80,
    "Cl": 1.75,
    "Ar": 1.88,
    "K": 2.75,
    "Ca": 2.31,
    "Sc": 2.11,
    "Ni": 1.63,
    "Cu": 1.40,
    "Zn": 1.39,
    "Ga": 1.87,
    "Ge": 2.11,
    "As": 1.85,
    "Se": 1.90,
    "Br": 1.85,
    "Kr": 8.8,
    "Rb": 3.03,
    "Sr": 2.49,
    "Pd": 1.63,
    "Ag": 1.72,
    "Cd": 1.58,
    "In": 1.93,
    "Sn": 2.17,
    "Sb": 2.06,
    "Te": 2.06,
    "I": 1.98,
    "Xe": 1.08,
    "Cs": 3.43,
    "Ba": 2.68,
    "Pr": 1.0,
    "Nd": 2.0,
    "Pt": 1.75,
    "Au": 1.66,
    "Hg": 1.55,
    "Tl": 1.96,
    "Pb": 2.02,
    "Bi": 2.07,
    "Po": 1.97,
    "At": 1.27,
    "Rn": 1.20
}

jdurrant's avatar
jdurrant committed
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
/**
 * Converts atom information to a 3dmoljs atom.
 * @param  {*} mol      The 3dmol.js molecule.
 * @param  {*} atomInf  Information about the atom.
 * @returns *  The 3dmoljs atom.
 */
function atomInfTo3DMolAtom(mol: any, atomInf: any): any {
    let selecteds = mol["selectedAtoms"]({
        "atom": atomInf["atomName"],
        "serial": atomInf["atomIndex"],
        "resi": atomInf["resID"]
    });
    let selected = selecteds[0];  //  Should be only one such atom.
    return selected;
}

jdurrant's avatar
jdurrant committed
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
/**
 * Gets the info about the atoms.
 * @param  {*} mol         The molecule with the atoms.
 * @param  {*} atomInfs    The atom information.
 * @param  {string} color  The highlighting color to use.
 * @returns *  A list of atom data ([atom,radius,color], [atom,radius,color],
 *             ...)
 */
function getAtomObjRadiusColor(mol: any, atomInfs: any, color: string): any[] {
    // Keep track of ligand and receptor coordinates where spheres should be
    // added.
    let atomObjsRadiiColors = [];

    for (let j = 0; j < atomInfs.length; j++) {
        // change the color for the atom
        let atomInf = atomInfs[j];
jdurrant's avatar
jdurrant committed
346

jdurrant's avatar
jdurrant committed
347
348
349
350
        if (idxOfAtomsSeen.has(atomInf["atomIndex"])) {
            continue;
        }
        idxOfAtomsSeen.add(atomInf["atomIndex"]);
jdurrant's avatar
jdurrant committed
351
        let atom = atomInfTo3DMolAtom(mol, atomInf);
jdurrant's avatar
jdurrant committed
352
353
354
355
356
357
358
359
360
361
362
363
364
365
        let radius = vdwRadii[atom["elem"]];
        radius = radius === undefined ? 1.5 : radius;

        atomObjsRadiiColors.push([atom, radius, color])
    }

    return atomObjsRadiiColors;
}

/**
 * Clears the previous interactions displayed in the 3Dmoljs viewer.
 * @returns void
 */
export function clearInteraction(): void {
jdurrant's avatar
jdurrant committed
366
    Store.store.commit("setVar", {
jdurrant's avatar
jdurrant committed
367
        name: "colorMessage",
jdurrant's avatar
jdurrant committed
368
        val: Store.defaultColorMsg
jdurrant's avatar
jdurrant committed
369
370
    });

jdurrant's avatar
jdurrant committed
371
372
373
374
    if (viewer === undefined) {
        return;
    }

jdurrant's avatar
jdurrant committed
375
376
377
378
379
    ThreeDMol.showSticksAsAppropriate();

    viewer["removeAllShapes"]();
    viewer["render"]();
}