BINANAInterface.ts 12.6 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
8
9

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

jdurrant's avatar
jdurrant committed
13
14
// 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
15
16
17
18
19
20
21
22
23
24
25
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
26
    ligandMol = lig;
jdurrant's avatar
jdurrant committed
27
28
29
30
31
32
33
34
35
}

/**
 * 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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
    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);

    return;

jdurrant's avatar
jdurrant committed
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
    let binana = window["binanaModule"];

    // Save to the fake file system
    binana["save_to_fake_fs"]("receptor.pdb", pdbtxt);
    binana["save_to_fake_fs"]("ligand.pdb", ligtxt);

    let params = ["-receptor", "receptor.pdb", "-ligand", "ligand.pdb"];

    const binanaParamNames = Object.keys(binanaParams);
    const binanaParamNamesLen = binanaParamNames.length;
    for (let i = 0; i < binanaParamNamesLen; i++) {
        const binanaParamName = binanaParamNames[i];
        const paramVal = binanaParams[binanaParamName];
        params.push("-" + binanaParamName);
        params.push(paramVal);
    }

    // Run binana
jdurrant's avatar
jdurrant committed
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
    jQuery("body").addClass("waiting");
    setTimeout(() => {
        binana["run"](params);
        jQuery("body").removeClass("waiting");

        // Get the json output.
        let json = binana["load_from_fake_fs"]("./ligand_receptor_output.json");

        binanaData = json;

        // Update the store too.
        Store.store.commit("setVar", {
            name: "jsonOutput",
            val: JSON.stringify(binanaData, undefined, 1)
        });
    }, 250);
jdurrant's avatar
jdurrant committed
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
}

/**
 * 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
113
    let colorMsg = Store.defaultColorMsg;
jdurrant's avatar
jdurrant committed
114
115
116
117
118
119
120
121
122
123
124

    // 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
125
        if (Store.store["state"]["colorByInteraction"] === Store.InteractionColoring.INTERACTION) {
jdurrant's avatar
jdurrant committed
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
            // 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
153
        Store.store.commit("setVar", {
jdurrant's avatar
jdurrant committed
154
155
156
157
            name: "colorMessage",
            val: colorMsg
        });

jdurrant's avatar
jdurrant committed
158
159
160
161
162
163
        ligAtomInfs = ligAtomInfs.concat(
            getAtomObjRadiusColor(ligandMol, ligandAtomInfs, ligColor)
        );
        recAtomInfs = recAtomInfs.concat(
            getAtomObjRadiusColor(receptorMol, receptorAtomInfs, recepColor)
        );
jdurrant's avatar
jdurrant committed
164
165
166
    }

    // Add spheres
jdurrant's avatar
jdurrant committed
167
168
169
170
171
172
173
    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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206

    // Render sticks of protein model too.
    ThreeDMol.showSticksAsAppropriate();
    receptorMol["setStyle"](
        {
            "index": recAtomInfs.map(
                i => i[0]["index"]
            ),
            "byres": true
        },
        {
            "stick": { "radius": 0.1 },  // 0.15
            "cartoon": { "color": 'spectrum' },
        }
    );

    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
207
            "opacity": 0.65
jdurrant's avatar
jdurrant committed
208
209
210
211
        });
    }
}

jdurrant's avatar
jdurrant committed
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
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
/**
 * 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
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
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
// 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
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
/**
 * 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
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
/**
 * 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
375

jdurrant's avatar
jdurrant committed
376
377
378
379
        if (idxOfAtomsSeen.has(atomInf["atomIndex"])) {
            continue;
        }
        idxOfAtomsSeen.add(atomInf["atomIndex"]);
jdurrant's avatar
jdurrant committed
380
        let atom = atomInfTo3DMolAtom(mol, atomInf)
jdurrant's avatar
jdurrant committed
381
382
383
384
385
386
387
388
389
390
391
392
393
394
        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
395
    Store.store.commit("setVar", {
jdurrant's avatar
jdurrant committed
396
        name: "colorMessage",
jdurrant's avatar
jdurrant committed
397
        val: Store.defaultColorMsg
jdurrant's avatar
jdurrant committed
398
399
    });

jdurrant's avatar
jdurrant committed
400
401
402
403
    if (viewer === undefined) {
        return;
    }

jdurrant's avatar
jdurrant committed
404
405
406
407
408
    ThreeDMol.showSticksAsAppropriate();

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