Skip to content
Snippets Groups Projects
Commit fdb6a976 authored by Nurul Nadzirin's avatar Nurul Nadzirin
Browse files

Merge branch 'dev' into 'master'

Dev

See merge request !1
parents 539f622c 4dab3b43
No related branches found
No related tags found
1 merge request!1Dev
Showing
with 4325 additions and 1058 deletions
......@@ -2,5 +2,4 @@
.mypy_cache
node_modules
build/*.js*
build/app*
\ No newline at end of file
build/*
\ No newline at end of file
......@@ -2,9 +2,7 @@ image: node:latest
before_script:
- npm install
- npm install --save-dev webpack
- node_modules/.bin/webpack --mode=production
- node_modules/.bin/tsc
- npm run build
pages:
script:
......
# RELEASE 0.1 - 28 January 2019
## RELEASE 0.2 - 24 February 2020
Component rembranded to `pdb-ligand-env`
### Features
* A lot of bug fixes and under the hood improvements
## RELEASE 0.1 - 28 January 2019
First release for public testing.
## Features
### Features
* Bound molecule interactions view
* Residue-level interactions view
......
README.md 0 → 100644
# PDB ligand environment component
This is a web-component to display ligand structure in 2D along with its interactions. Ligand can be perceived as a set of covalently linked pdb residues (refered to as bound molecule) or a single pdb residue. This depiction can be enriched with a substructure highlight and binding site interactions.
## Step after cloning (use local server to see demo pages)
```shell
npm run-script build
cp demo/demo* build/ (this copies demo_component.html and demo_plugin.html to build folder)
cd build
python3 -m http.server
```
## Component modes
* Mode A: Display bound molecule and its interactions
* Mode B: Display ligand and its interactions
* Mode C: Display ligand (chemical component) only
| Mode A | Mode B | Mode C |
|:------------------: | :-------: | :-------: |
| <img src="https://www.ebi.ac.uk/~lpravda/imgs/1cbs_REA_200_A.png"/>| <img src="https://www.ebi.ac.uk/~lpravda/imgs/3d12_bm1.png"/> | <img src="https://www.ebi.ac.uk/pdbe-srv/pdbechem/image/showNew?code=VIA&size=500"/> |
| [1cbs REA 200 A](https://www.ebi.ac.uk/pdbe/entry/pdb/1cbs/bound/REA) | 3D12 bm1 (`2xGLC-2xBGC-LXZ-NGA-GL0`)| [wwPDB CCD - VIA](https://pdbe.org/chem/VIA)
## How to use it
The component can be inserted into the pages by two different ways. Either as a `web-component` using html tag, or directly by using javascript as a `plugin`.
### Web-component
A few files needs to be imported in the page before the component is attempted to be loaded:
```html
<!-- D3 -->
<script src="https://d3js.org/d3.v5.min.js"></script>
<!-- CSS style to be used for scene drawing (required for saving SVGs.) -->
<link rel="stylesheet" href="pdb-ligand-env-svg.css" />
<!-- UI icons -->
<link rel="stylesheet" href="https://ebi.emblstatic.net/web_guidelines/EBI-Icon-fonts/v1.3/fonts.css" />
<!-- Web component polyfill (only loads what it needs) -->
<script src="https://cdn.jsdelivr.net/npm/@webcomponents/webcomponentsjs/webcomponents-lite.js" charset="utf-8"></script>
<script src="https://cdn.jsdelivr.net/npm/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"
charset="utf-8"></script>
<!--PDBe interactions component-->
<script type="module" src="pdb-ligand-env-component-0.2.0-min.js"></script>
```
#### A) Ligand interactions
```html
<pdb-ligand-env pdb-id="1cbs" pdb-res-id="200" pdb-chain-id="A"></pdb-ligand-env>
```
#### B) Bound molecule interactions
```html
<pdb-ligand-env pdb-id="3d12" bound-molecule-id="bm1"></pdb-ligand-env>
```
#### C) Ligand/chemical component
```html
<pdb-ligand-env pdb-res-name="CLR" zoom-on ></pdb-ligand-env>
```
The component contains a number of properties that can be set, in order to change data that are being displayed. First you need to define a component on the page:
```html
<pdb-ligand-env id='SIA-component'></pdb-ligand-env>
```
and then inject data you want to display e.g.:
```javascript
let chemUrl = `https://www.ebi.ac.uk/pdbe/static/files/pdbechem_v2/SIA/annotation`;
let interactionsURL = "https://wwwdev.ebi.ac.uk/pdbe/graph-api/pdb/bound_ligand_interactions/4yy1/A/604";
let component = document.getElementById('SIA-component');
const depiction = await (await fetch(chemUrl)).json();
const interactionsData = await (await fetch(interactionsURL)).json();
const atomsToHighlight = ['C10', 'C11', 'O10'];
component.depiction = depiction;
component.ligandHighlight = atomsToHighlight;
component.interactions = interactionsData;
```
### Plugin
The component can be also added to DOM directly from JavaScript. There are some requirements
```html
<!-- D3 -->
<script src="https://d3js.org/d3.v5.min.js"></script>
<!-- CSS style to be used for scene drawing (required for saving SVGs.) -->
<link rel="stylesheet" href="pdb-ligand-env-svg.css" />
<!-- UI icons -->
<link rel="stylesheet" href="https://ebi.emblstatic.net/web_guidelines/EBI-Icon-fonts/v1.3/fonts.css" />
<!--PDB ligand environment plugin-->
<script src="pdb-ligand-env-plugin-min.js"></script>
<link rel="stylesheet" href="pdb-ligand-env.css" />
```
and then the component can be instantiated as simply as:
```javascript
let component = document.getElementById('SIA-component');
let uiParams = {
reinitialize: true, // allow reinitialize option in the component menu
zoom: true, // allow scene zoom
fullScreen: true, // allow allow full screen option in the component menu
downloadImage: true, // allow image download from the component menu
downloadData: true, // allow interactions data download from compoment menu
center: true, // allow scene centering option from the component menu
help: false, // allow help option from the component menu
residueLabel: true, // show residue label
tooltip: true // show residue tooltip on mouse hover
};
this.display = new Visualization(this, uiParams);
// to display bound molecule interactions
this.display.initBoundMoleculeInteractions('3d12', 'bm1');
// to display ligand interactions
this.display.initLigandInteractions('1cbs', 200, 'A');
// to display chemical component only
this.display.initLigandDisplay('HEM');
````
## Parameters
| Parametr | Type | Required | Description |
|-------------------- | --------- | -------- | ------- |
| pdb-id | string | No | PDB id of a protein to retrieve interactions from. `(mode A and B only)` |
| bound-molecule-id | string | No | PDB bound molecule id `(mode A only)` |
| pdb-res-name | string | No | PDB residue name aka: *auth_comp_id* `(mode C only)`
| pdb-res-id | number | No | PDB residue id aka: *auth_seq_id* `(mode B only)`
| pdb-chain-id | string | No | PDB residue chain aka: *auth_asym_id* `(mode B only)`|
| substructure | string[] | No | List of atom names to be highlighted on the ligand structure |
| color | string | No | HEX representation of the color highlight. `(Default: #D3D3D3)` |
| zoom-on | boolean | No | Allow zoom functionality on the component level. |
<!doctype html>
<html lang="en">
<head>
<script src="https://d3js.org/d3.v5.min.js"></script>
<!-- CSS style to be used for scene drawing (required for saving SVGs.) -->
<link rel="stylesheet" href="pdb-ligand-env-svg.css" />
<!-- UI icons -->
<link rel="stylesheet" href="https://ebi.emblstatic.net/web_guidelines/EBI-Icon-fonts/v1.3/fonts.css" />
<!-- Web component polyfill (only loads what it needs) -->
<script src="https://cdn.jsdelivr.net/npm/@webcomponents/webcomponentsjs/webcomponents-lite.js"
charset="utf-8"></script>
<script src="https://cdn.jsdelivr.net/npm/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"
charset="utf-8"></script>
<!--PDBe interactions component-->
<script type="module" src="pdb-ligand-env-component-0.2.0-min.js"></script>
</head>
<body>
<!--Mode A-->
<div style="position: relative; float: left;">
<div style="width: 500px; height: 500px; position: relative">
<pdb-ligand-env pdb-id="1cbs" pdb-res-id="200" pdb-chain-id="A"></pdb-ligand-env>
</div>
</div>
<!--Mode B-->
<div style="position: relative; float: left;">
<div style="width: 500px; height: 500px; position: relative">
<pdb-ligand-env pdb-id="3d12" bound-molecule-id="bm1"></pdb-ligand-env>
</div>
</div>
<!--Mode C-->
<div style="position: relative; float: left;">
<div style="width: 500px; height: 500px; position: relative">
<pdb-ligand-env pdb-res-name="CLR" zoom-on></pdb-ligand-env>
</div>
</div>
</body>
</html>
\ No newline at end of file
<!doctype html>
<html lang="en">
<head>
<link rel="stylesheet" href="pdb-ligand-env.css" />
<script src="https://d3js.org/d3.v5.min.js"></script>
<!-- CSS style to be used for scene drawing (required for saving SVGs.) -->
<link rel="stylesheet" href="pdb-ligand-env-svg.css" />
<!-- UI icons -->
<link rel="stylesheet" href="https://ebi.emblstatic.net/web_guidelines/EBI-Icon-fonts/v1.3/fonts.css" />
<!-- Web component polyfill (only loads what it needs) -->
<script src="https://cdn.jsdelivr.net/npm/@webcomponents/webcomponentsjs/webcomponents-lite.js"
charset="utf-8"></script>
<script src="https://cdn.jsdelivr.net/npm/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"
charset="utf-8"></script>
<!--PDBe interactions plugin-->
<script src="pdb-ligand-env-plugin-min.js"></script>
<!--Initialize Visualization-->
<script>
document.addEventListener("DOMContentLoaded", function (event) {
//<!--Mode A-->
var eleLigInteraction = document.getElementById('ligand-interaction');
this.lidisplay = new Visualization(eleLigInteraction, undefined);
this.lidisplay.initLigandInteractions('1cbs', 200, 'A');
//<!--Mode B-->
var eleBoundInteraction = document.getElementById('boundmolecule-interaction');
this.bmdisplay = new Visualization(eleBoundInteraction, undefined);
this.bmdisplay.initBoundMoleculeInteractions('3d12', 'bm1');
//<!--Mode C-->
var eleChemComp = document.getElementById('chem-comp');
this.ccdisplay = new Visualization(eleChemComp, undefined);
this.ccdisplay.initLigandDisplay('HEM');
});
</script>
</head>
<body>
<!--Mode A-->
<div style="position: relative; float: left;">
<div style="width: 600px; height: 600px; position: relative">
<div id='ligand-interaction'></div>
</div>
</div>
<!--Mode B-->
<div style="position: relative; float: left;">
<div style="width: 600px; height: 600px; position: relative">
<div id='boundmolecule-interaction'></div>
</div>
<!--Mode C-->
<div style="position: relative; float: left;">
<div style="width: 600px; height: 600px; position: relative">
<div id='chem-comp'></div>
</div>
</div>
</body>
</html>
\ No newline at end of file
File moved
......@@ -3,8 +3,7 @@
<script src="https://d3js.org/d3.v5.min.js"></script>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<!--testing purposes only-->
<link rel="stylesheet" href="pdbe-interactions-styles.css" />
<link rel="stylesheet" href="pdbe-interactions-svgstyles.css" />
<link rel="stylesheet" href="pdb-ligand-env-svg.css" />
<link rel="stylesheet" href="https://ebi.emblstatic.net/web_guidelines/EBI-Icon-fonts/v1.3/fonts.css" />
<!-- MOL* -->
......@@ -12,8 +11,6 @@
href="https://wwwdev.ebi.ac.uk/pdbe/pdb-component-library/v1.0/css/molstar-light-0.0.1.css">
<script src="https://wwwdev.ebi.ac.uk/pdbe/pdb-component-library/v1.0/js/molstar-0.0.1.js"></script>
<script src="app.js"></script>
<!-- Web component polyfill (only loads what it needs) -->
<script src="https://cdn.jsdelivr.net/npm/@webcomponents/webcomponentsjs/webcomponents-lite.js" charset="utf-8">
</script>
......@@ -21,16 +18,17 @@
<script src="https://cdn.jsdelivr.net/npm/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"
charset="utf-8"></script>
<script type="module" src="component.js"></script>
<script type="module" src="pdb-ligand-env-component-0.2.0-min.js"></script>
<script>
var renderBmInteractions = function (id, bmId) {
var int = `<pdb-interactions pdb-id="${id}" bound-molecule-id="${bmId}"></pdb-interactions>`
var int =
`<pdb-ligand-env style="border: 1px solid black" pdb-id="${id}" bound-molecule-id="${bmId}"></pdb-ligand-env>`
document.getElementById('rt').innerHTML = int;
};
var renderLigandInteractions = function (id, chain, resId) {
var int =
`<pdb-interactions pdb-id="${id}" pdb-chain-id="${chain}" pdb-res-id="${resId}"></pdb-interactions>`
`<pdb-ligand-env style="border: 1px solid black" pdb-id="${id}" pdb-chain-id="${chain}" pdb-res-id="${resId}" zoom-on></pdb-ligand-env>`
document.getElementById('rt').innerHTML = int;
};
......@@ -71,8 +69,6 @@
query: "https://wwwdev.ebi.ac.uk/pdbe/coordinates/1cbs/ligandInteraction?&authAsymId=$A&authSeqNumber=$200&radius=5&dataSource=hydrogens"
};
}
console.log('Perceived parameters:');
console.log(params);
if (params.bmid === undefined) {
renderLigandInteractions(params.pdbid, params.chain, params.resid);
......@@ -94,7 +90,11 @@
showPDBeLogo: false,
assemblyId: 'preferred', //'deposited'
// selectInteraction: false,
ligandView: {auth_asym_Id: params.chain, auth_seq_id: params.resid, hydrogens: true},
ligandView: {
auth_asym_Id: params.chain,
auth_seq_id: params.resid,
hydrogens: true
},
backgroundColour: '0xFFFFFF',
//customColorList: [0xff0000, 0x0000ff],
//representationStyle: representationStyle//,
......@@ -112,25 +112,30 @@
<body>
<div style="position: relative; float: left;">
<div id="rt" style="width: 500px; height: 500px; border: 1px solid black; position: relative">
<div id="rt" style="width: 500px; height: 500px; position: relative">
</div>
</div>
<div style="position: relative; float: left;">
<div id='3dViewer' style="position:relative; width: 500px;height: 500px;"></div>
</div>
<!-- <div style="position: relative; float: left;">
<div id="rt" style="width: 500px; height: 500px; border: 1px solid black; position: relative">
<pdb-interactions pdb-id="3d11" bound-molecule-id="bm1"></pdb-interactions>
</div> -->
<div style="position: relative; float: left;">
<div id="rt 1" style="width: 500px; height: 500px; position: relative">
<pdb-ligand-env pdb-id="3d12" bound-molecule-id="bm1" zoom-on></pdb-ligand-env>
</div>
</div>
<div style="position: relative; float: left;">
<div id="rt 1" style="width: 500px; height: 500px; position: relative">
<pdb-ligand-env pdb-id="2aw3" bound-molecule-id="bm1" zoom-on></pdb-ligand-env>
</div>
</div>
<!--
Further use in the app for bound molecule interactions:
<pdb-interactions pdb-id="3d12" bound-molecule-id="bm1"></pdb-interactions>
<pdb-ligand-env pdb-id="3d12" bound-molecule-id="bm1"></pdb-ligand-env>
for ligand interactions:
<pdb-interactions pdb-id="3d12" pdb-chain-id="A" pdb-res-id="200"></pdb-interactions>
<pdb-ligand-env pdb-id="3d12" pdb-chain-id="A" pdb-res-id="200"></pdb-ligand-env>
-->
<script>
......
This diff is collapsed.
const gulp = require('gulp');
const path = require('path');
const del = require('del');
const concat = require('gulp-concat');
const header = require('gulp-header');
const minify = require("gulp-minify");
const PACKAGE_ROOT_PATH = process.cwd();
const PKG_JSON = require(path.join(PACKAGE_ROOT_PATH, "package.json"));
const banner = ['/**',
` * ${PKG_JSON.name}`,
` * @version ${PKG_JSON.version}`,
' * @link https://gitlab.ebi.ac.uk/pdbe/web-components/ligand-env',
' * @license Apache 2.0',
' */',
''
].join('\n');
const license = ['/**',
' * Copyright 2019-2020 Lukas Pravda <lpravda@ebi.ac.uk>',
' * European Bioinformatics Institute (EBI, http://www.ebi.ac.uk/)',
' * European Molecular Biology Laboratory (EMBL, http://www.embl.de/)',
' * Licensed under the Apache License, Version 2.0 (the "License");',
' * you may not use this file except in compliance with the License.',
' * You may obtain a copy of the License at ',
' * http://www.apache.org/licenses/LICENSE-2.0',
' * ',
' * Unless required by applicable law or agreed to in writing, software',
' * distributed under the License is distributed on an "AS IS" BASIS, ',
' * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.',
' * See the License for the specific language governing permissions and ',
' * limitations under the License.',
' */',
''
].join('\n');
gulp.task('clean', function () {
return del([`build/${PKG_JSON.name}-component-${PKG_JSON.version}.js`, '!build']);
});
gulp.task('concatCSS', () => {
return gulp.src(['src/styles/pdb-ligand-env-svg.css'])
.pipe(concat(`${PKG_JSON.name}-svg.css`))
.pipe(header(license, {}))
.pipe(header(banner, {}))
.pipe(gulp.dest('build/'));
});
gulp.task('copyIndex', () => {
return gulp.src(['dependencies/index.html'])
.pipe(concat(`index.html`))
.pipe(gulp.dest('build/'));
});
gulp.task('copyMapping', () => {
return gulp.src(['dependencies/het_mapping.json'])
.pipe(concat(`het_mapping.json`))
.pipe(gulp.dest('build/'));
});
gulp.task('copyXML', () => {
return gulp.src(['dependencies/pdb-snfg-visuals.xml'])
.pipe(concat(`pdb-snfg-visuals.xml`))
.pipe(gulp.dest('build/'));
});
gulp.task('concat', () => {
return gulp.src([`build/${PKG_JSON.name}-plugin.js`, `build/${PKG_JSON.name}-component-init.js`])
.pipe(concat(`${PKG_JSON.name}-component-${PKG_JSON.version}.js`))
.pipe(header(license, {}))
.pipe(header(banner, {}))
.pipe(minify({
noSource: true
}))
.pipe(gulp.dest('build/'));
});
gulp.task('minifyPlugin', () => {
return gulp.src([`build/${PKG_JSON.name}-plugin.js`])
.pipe(concat(`${PKG_JSON.name}-plugin.js`))
.pipe(header(license, {}))
.pipe(header(banner, {}))
.pipe(minify({
noSource: true
}))
.pipe(gulp.dest('build/'));
});
gulp.task('default', gulp.series('clean', 'concat', 'concatCSS',
'copyXML', 'copyIndex', 'copyMapping', 'minifyPlugin'));
\ No newline at end of file
This diff is collapsed.
{
"name": "interactions",
"version": "1.0.0",
"name": "pdb-ligand-env",
"version": "0.2.0",
"description": "",
"main": "app.js",
"dependencies": {
"@types/d3": "^5.0.1",
"@types/d3-tip": "^3.5.5",
"d3": "^5.7.0",
"d3": "^5.15.0",
"d3-tip": "^0.9.1",
"d3scription": "^1.0.1",
"lit-element": "^2.1.0",
"typescript": "^3.1.6"
"lit-element": "^2.2.1",
"typescript": "^3.7.4"
},
"devDependencies": {
"@babel/core": "^7.3.3",
"@babel/plugin-transform-runtime": "^7.2.0",
"@babel/preset-env": "^7.3.1",
"@babel/runtime": "^7.3.1",
"@webcomponents/webcomponentsjs": "^2.1.3",
"@babel/core": "^7.8.3",
"@babel/plugin-transform-runtime": "^7.8.3",
"@babel/preset-env": "^7.8.3",
"@babel/runtime": "^7.8.3",
"@webcomponents/webcomponentsjs": "^2.4.1",
"babel-loader": "^8.0.5",
"browser-sync": "^2.26.7",
"camelcase": "^5.0.0",
"clean-webpack-plugin": "^1.0.1",
"css-loader": "^2.1.0",
"del": "^5.1.0",
"eslint": "^4.12.0",
"file-loader": "^3.0.1",
"gulp": "^4.0.2",
"gulp-concat": "^2.6.1",
"gulp-header": "^2.0.9",
"gulp-minify": "^3.1.0",
"live-server": "^1.2.1",
"npm-run-all": "^4.1.3",
"onchange": "^6.1.0",
"style-loader": "^0.23.1",
"ts-node": "^7.0.1",
"url-loader": "^1.1.2",
......@@ -34,12 +40,12 @@
"webpack-cli": "^3.3.10"
},
"scripts": {
"tsc:w": "tsc -w",
"prestart": "tsc",
"serve": "live-server build --watch=src/**/*.html,src/bundle.js,src/**/*.css",
"start": "npm-run-all --parallel serve tsc:w",
"webpack": "webpack",
"buildComponent": "webpack --mode=development"
"tscW": "tsc -w",
"serve": "live-server build --watch=build",
"start": "npm-run-all --parallel watch serve",
"build": "tsc && webpack --mode=development && gulp",
"watch": "onchange 'src/**/*' -- npm run build",
"buildProduction": "tsc && webpack --mode=production && gulp"
},
"author": "Lukas Pravda",
"license": "Apache License 2.0"
......
// Import the LitElement base class and html helper function
import { LitElement, html } from 'lit-element';
// Extend the LitElement base class
class pdbInteractions extends LitElement {
//Get properties / attribute values
static get properties() {
return {
'pdb-id': { type: String },
'bound-molecule-id': { type: String },
'pdb-res-id': { type: Number },
'pdb-chain-id': { type: String }
};
}
constructor() {
super();
}
render() {
return html`
${!this['pdb-id'] ?
html`<div style='text-align:center;font-weight:bold;margin-top:20px;'>Error: Specify valid 'pdb-id' attribute to render the component.<div>` :
html``
}
`;
}
updated(changedProperties) {
if (this['pdb-id']) {
this.display = new Visualization(this);
if (this['bound-molecule-id']) {
this.display.initialiseBoundMolecule(this['pdb-id'], this['bound-molecule-id']);
}
else {
this.display.initialiseLigand(this['pdb-id'], this['pdb-res-id'], this['pdb-chain-id'])
}
}
}
connectedCallback() {
super.connectedCallback();
this.style.display = 'block';
this.style.height = '100%';
this.style.width = '100%';
this.style.position = 'relative';
}
createRenderRoot() {
/**
* Render template in light DOM. Note that shadow DOM features like
* encapsulated CSS are unavailable.
*/
return this;
}
}
// Register the new element with the browser.
customElements.define('pdb-interactions', pdbInteractions);
\ No newline at end of file
// Import the LitElement base class and html helper function
import { LitElement, html } from 'lit-element';
import "../styles/pdb-ligand-env.css";
// Extend the LitElement base class
class pdbLigandEnv extends LitElement {
//Get properties / attribute values
static get properties() {
return {
pdbId: { type: String, attribute: 'pdb-id' },
bmId: { type: String, attribute: 'bound-molecule-id' },
resName: { type: String, attribute: 'pdb-res-name' },
resId: { type: Number, attribute: 'pdb-res-id' },
chainId: { type: String, attribute: 'pdb-chain-id' },
substructureHighlight: { type: Array, attribute: 'substructure' },
substructureColor: { type: String, attribute: 'color' },
zoomOn: { type: Boolean, attribute: 'zoom-on' },
};
}
constructor() {
super();
}
async connectedCallback() {
let uiParams = new Config.UIParameters();
uiParams.zoom = this.zoomOn;
this.display = new Visualization(this, uiParams);
if (this.pdbId) {
if (this.bmId) {
this.display.initBoundMoleculeInteractions(this.pdbId, this.bmId);
}
else {
this.display.initLigandInteractions(this.pdbId, this.resId, this.chainId);
}
}
else if (this.resName) {
this.display.initLigandDisplay(this.resName).then(() => this.display.centerScene());
}
}
//#region properties
set depiction(data) {
if (!data) return;
this.display.addDepiction(data, false);
this.display.centerScene();
}
set ligandHighlight(data) {
if (!data || !this.display) {
console.log(`Argument needs to be a non empty array of strings.`);
return;
}
this.display.addLigandHighlight(data, this.highlightColor);
this.substructureHighlight = data;
}
set highlightColor(data) {
if (!data || !this.display) return;
this.highlightColor = data;
this.display.addLigandHighlight(this.substructureHighlight, this.highlightColor);
}
set contourData(data) {
if (!data || !this.display || !this.display.depiction !== undefined ) return;
this.display.addContours(data);
}
set zoom(data) {
if (this.display !== undefined) this.display.toogleZoom(data);
}
//#endregion properties
createRenderRoot() {
/**
* Render template in light DOM. Note that shadow DOM features like
* encapsulated CSS are unavailable.
*/
return this;
}
}
// Register the new element with the browser.
customElements.define('pdb-ligand-env', pdbLigandEnv);
\ No newline at end of file
namespace Config {
export const nodeSize: number = 30;
export const interactionClickEvent: string = 'PDB.interactions.click';
export const interactionMouseoverEvent: string = 'PDB.interactions.mouseover';
export const interactionMouseoutEvent: string = 'PDB.interactions.mouseout'
export const interactionShowLabelEvent: string = 'PDB.interactions.showLabel';
export const interactionHideLabelEvent: string = 'PDB.interactions.hideLabel';
export const molstarClickEvent: string = 'PDB.molstar.click';
export const molstarMouseoverEvent: string = 'PDB.molstar.mouseover';
export const molstarMouseoutEvent: string = 'PDB.molstar.mouseout';
export const aaTypes = new Map<string, Array<string>>([
['hydrophobic', new Array<string>('A', 'I', 'L', 'M', 'F', 'W', 'V')],
['positive', Array<string>('K', 'R', 'O')],
['negative', Array<string>('E', 'D')],
['polar', Array<string>('N', 'Q', 'S', 'T')],
['cystein', Array<string>('C', 'U')],
['glycine', Array<string>('G')],
['proline', Array<string>('P')],
['aromatic', Array<string>('H', 'Y')]
]);
export const aaAbreviations = new Map<string, string>([
['ALA', 'A'],
['ARG', 'R'],
['ASN', 'N'],
['ASP', 'D'],
['CYS', 'C'],
['GLU', 'E'],
['GLN', 'Q'],
['GLY', 'G'],
['HIS', 'H'],
['ILE', 'I'],
['LEU', 'L'],
['LYS', 'K'],
['MET', 'M'],
['PHE', 'F'],
['PRO', 'P'],
['SER', 'S'],
['THR', 'T'],
['TRP', 'W'],
['TYR', 'Y'],
['VAL', 'V']
]);
export const backboneAtoms: Array<string> = ['N', 'CA', 'C', 'O'];
export const interactionsClasses = new Map<string, Array<string>>([
["covalent", new Array<string>("covalent")],
["electrostatic", new Array<string>("ionic", "hbond", "weak_hbond", "polar", "weak_polar", "xbond", "carbonyl")],
['amide', new Array<string>("AMIDEAMIDE", "AMIDERING")],
["vdw", new Array<string>("vdw")],
["hydrophobic", new Array<string>("hydrophobic")],
["aromatic", new Array<string>("aromatic", "FF", "OF", "EE", "FT", "OT", "ET", "FE", "OE", "EF")],
["atom-pi", new Array<string>("CARBONPI", "CATIONPI", "DONORPI", "HALOGENPI", "METSULPHURPI")],
["metal", new Array<string>("metal_complex")],
["clashes", new Array<string>("clash", "vdw_clash")]
]);
export class UIParameters {
reinitialize: boolean;
zoom: boolean;
fullScreen: boolean;
downloadImage: boolean;
downloadData: boolean;
center: boolean;
help: boolean;
residueLabel: boolean;
tooltip: boolean;
constructor() {
this.reinitialize = true;
this.zoom = true;
this.fullScreen = true;
this.downloadImage = true;
this.downloadData = true;
this.center = true;
this.help = true;
this.residueLabel = true;
this.tooltip = true;
}
}
}
\ No newline at end of file
......@@ -17,9 +17,21 @@ class Depiction {
ccdId: string;
atoms: Atom[];
bonds: Bond[];
resolution: Vector2D;
constructor(data: any) {
private root: d3.Selection<SVGGElement, unknown, null, undefined>;
private structure: d3.Selection<SVGGElement, unknown, null, undefined>;
private contour: d3.Selection<SVGGElement, unknown, null, undefined>;
private highlight: d3.Selection<SVGGElement, unknown, null, undefined>;
constructor(parent: any, data: any) {
this.root = parent
this.highlight = this.root.append('g').attr('id', 'highlight');
this.structure = this.root.append('g').attr('id', 'structure');
this.contour = this.root.append('g').attr('id', 'contour');
this.ccdId = data.ccd_id;
this.resolution = new Vector2D(data.resolution.x, data.resolution.y);
......@@ -76,16 +88,55 @@ class Depiction {
return new Vector2D(x, y);
}
public draw(withClarityNodes: boolean = false) {
this.structure.selectAll("*").remove();
if (withClarityNodes) this.appendClarityNodes();
this.appendBondVisuals();
this.appendTexts();
}
public highlightSubgraph(atoms: Array<string>, color: string = undefined) {
if (!this.atoms || !atoms) return;
this.highlight.selectAll('*').remove();
color = color ? color : "#BFBFBF";
let atomsToHighlight = this.atoms.filter(x => atoms.includes(x.name));
this.highlight.selectAll()
.data(atomsToHighlight)
.enter()
.append('circle')
.attr('r', '16.12')
.attr('cx', x => x.position.x)
.attr('cy', x => x.position.y)
.attr('style', `fill:${color};fill-rule:evenodd;stroke:${color};stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1`);
let bondsToHighlight = this.bonds.filter(x => atoms.includes(x.bgn.name) && atoms.includes(x.end.name))
this.highlight.selectAll()
.data(bondsToHighlight)
.enter()
.append('path')
.attr('d', x => `M ${x.bgn.position.x},${x.bgn.position.y} ${x.end.position.x},${x.end.position.y}`)
.attr('style', `fill:none;fill-rule:evenodd;stroke:${color};stroke-width:22px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1`)
}
public addContour(data: any) {
this.contour.selectAll('*').remove();
this.contour.append('div').text(`'contour data goes here: ${data}`);
}
/**
* Appends to a given selection the visual representation of bonds as svg:path elements.
*
* @param {d3.Selection<d3.BaseType, {}, HTMLElement, any>} selection where to append SVG
* representation of the bond visuals.
* @memberof Depiction
*/
public appendBondVisualsTo(root: d3.Selection<d3.BaseType, {}, HTMLElement, any>): void {
root.selectAll()
private appendBondVisuals(): void {
this.structure.selectAll()
.data(this.bonds)
.enter()
.append('path')
......@@ -100,12 +151,10 @@ class Depiction {
* Also there are all sorts of colorful subscripts and superscripts,
* so it is much easier to use it this way.
*
* @param {d3.Selection<d3.BaseType, {}, HTMLElement, any>} root where to append SVG
* representation of compounds' labels.
* @memberof Depiction
*/
public appendTextsTo(root: any): void {
root.selectAll()
private appendTexts(): void {
this.structure.selectAll()
.data(this.atoms.filter(x => Object.keys(x.label).length !== 0).map(x => x.label))
.enter()
.append('text')
......@@ -126,23 +175,22 @@ class Depiction {
* Add small white circle on the background of atoms with label
* just to make the interaction lines pretty.
*
* @param {*} root
* @memberof Depiction
*/
public appendClarityNodes(root: any): void {
root.selectAll()
private appendClarityNodes(): void {
this.structure.selectAll()
.data(this.atoms.filter(x => Object.keys(x.label).length != 0))
.enter().append('circle')
.attr('class', 'svg-shadow-node')
.classed('pdb-lig-env-svg-shadow-node', true)
.attr('cx', (x: any) => x.position.x)
.attr('cy', (x: any) => x.position.y)
.attr('r', 15);
}
public getCenter(ids: string[]): Vector2D {
let coords = new Array<Vector2D>();
ids.forEach(x => {
let pos = this.atoms.find(y => y.name === x).position;
coords.push(pos);
......
This diff is collapsed.
namespace Model {
"use strict";
const aminoAcids = new Map<string, Array<string>>([
['cystein', new Array<string>('CYS', 'SEC', 'CSO')],
['positive', new Array<string>('LYS', 'ARG')],
['negative', new Array<string>('ASP', 'GLU')],
['polar', new Array<string>('SER', 'SEP', 'THR', 'ASN', 'GLN', 'TPO')],
['aliphatic', new Array<string>('ALA', 'VAL', 'ILE', 'LEU', 'MET', 'PRO', 'MSE')],
['aromatic', new Array<string>('TRP', 'TYR', 'PHE', 'HIS')]
]);
const backboneAtoms = ['N', 'CA', 'C', 'O'];
const interactionsClasses = new Map<string, Array<string>>([
["covalent", new Array<string>("covalent")],
["electrostatic", new Array<string>("ionic", "hbond", "weak_hbond", "polar", "weak_polar", "xbond", "carbonyl")],
['amide', new Array<string>("AMIDEAMIDE", "AMIDERING")],
["vdw", new Array<string>("vdw")],
["hydrophobic", new Array<string>("hydrophobic")],
["aromatic", new Array<string>("aromatic", "FF", "OF", "EE", "FT", "OT", "ET", "FE", "OE", "EF", "CARBONPI", "CATIONPI", "DONORPI", "HALOGENPI", "METSULPHURPI")],
["metal", new Array<string>("metal_complex")],
["clashes", new Array<string>("clash", "vdw_clash")]
]);
export class UIParameters {
reinitialize: boolean;
downloadImage: boolean;
downloadData: boolean;
center: boolean;
help: boolean;
residueLabel: boolean;
tooltip: boolean;
constructor() {
this.reinitialize = true;
this.downloadImage = true;
this.downloadData = true;
this.center = true;
this.help = true;
this.residueLabel = true;
this.tooltip = true;
}
}
/**
*
*
......@@ -104,7 +61,7 @@ namespace Model {
this.chemCompId = data.chem_comp_id;
this.authorInsertionCode = data.author_insertion_code;
this.id = `${this.chainId}${this.authorResidueNumber}${(this.authorInsertionCode === ' ' ? '' : this.authorInsertionCode)}`;
this.isLigand = isLigand
this.isLigand = isLigand;
}
/**
......@@ -120,13 +77,15 @@ namespace Model {
return 'water';
}
for (let [key, value] of aminoAcids) {
if (value.includes(this.chemCompId)) {
let code = ResidueProvider.getInstance().getAminoAcidAbbreviation(this.chemCompId);
for (let [key, value] of Config.aaTypes) {
if (value.includes(code)) {
return key;
}
}
return 'others';
return 'other';
}
public equals(other: Residue): boolean {
......@@ -143,7 +102,8 @@ namespace Model {
export class InteractionNode implements d3.SimulationNodeDatum {
id: string;
residue: Residue;
scale: number;
scale: number; // how is the node scaled up in comparison to its original size
static: boolean // whether or not can be dragged and droped
index?: number;
x?: number;
......@@ -156,6 +116,7 @@ namespace Model {
constructor(r: Residue, scale: number, id: string, x: number = undefined, y: number = undefined) {
this.residue = r;
this.id = id;
this.static = Boolean(scale < 1.0);
this.scale = scale;
if (x !== undefined) {
......@@ -188,6 +149,7 @@ namespace Model {
target: InteractionNode;
getLinkClass(): string;
hasClash(): boolean;
toTooltip(): string;
}
......@@ -215,6 +177,17 @@ namespace Model {
return this.target.equals(n) || this.source.equals(n);
}
public hasClash(): boolean {
this.interactions.forEach(x => {
if (x.includes('clash')) {
return true
}
});
return false;
}
/**
* Shorthand to check if the link contains particular underlying
* residue.
......@@ -247,7 +220,7 @@ namespace Model {
if (this.isBoundMoleculeLink()) return 'ligand';
// JS map preserves order of elements
for (let [key, value] of interactionsClasses) {
for (let [key, value] of Config.interactionsClasses) {
for (let interactionDetails of this.interactions.values()) {
if (interactionDetails.filter(x => -1 !== value.indexOf(x)).length > 0) {
return key;
......@@ -299,7 +272,7 @@ namespace Model {
// JS map preserves order of elements
let allInteractions = this.interaction.map(x => x.interactionsClases).reduce((x, y) => x.concat(y));
for (let [key, value] of interactionsClasses) {
for (let [key, value] of Config.interactionsClasses) {
if (allInteractions.filter(x => -1 !== value.indexOf(x)).length > 0) {
return key;
}
......@@ -324,15 +297,23 @@ namespace Model {
}
public toTooltip() {
let msg = [];
let msg = new Set();
let rProvider = ResidueProvider.getInstance();
this.interaction.forEach(x => {
let isMainChain = !this.target.residue.isLigand && x.targetAtoms.every(y => backboneAtoms.includes(y));
let interactionFlag = isMainChain ? 'backbone' : 'side chain';
let isMainChain = !this.target.residue.isLigand && x.targetAtoms.every(y => Config.backboneAtoms.includes(y));
let targetAbbreviation = rProvider.getAminoAcidAbbreviation(this.target.residue.chemCompId);
let isResidue = Boolean(targetAbbreviation !== 'X');
let interactionFlag = '';
if (isMainChain && isResidue) interactionFlag = 'backbone';
else if (!isMainChain && isResidue) interactionFlag = 'side chain';
else interactionFlag = 'ligand';
msg.push(`<li><span>${interactionFlag}</span> interaction (<b>${x.targetAtoms}</b> | ${x.interactionsClases}): ${x.distance}Å</li>`);
msg.add(`<li><span>${interactionFlag}</span> interaction (<b>${x.targetAtoms}</b> | ${x.interactionsClases}): ${x.distance}Å</li>`);
});
return `<ul>${msg.join('\n')}</ul>`;
return `<ul>${Array.from(msg.values()).join('\n')}</ul>`;
}
}
......@@ -350,6 +331,8 @@ namespace Model {
interactionNodes: InteractionNode[];
links: Link[];
private tmpResidueSet: ObjectSet<Residue>;
private tmpNodesSet: ObjectSet<InteractionNode>;
constructor() {
this.residues = new Array<Residue>();
......@@ -362,36 +345,30 @@ namespace Model {
this.pdbId = pdbId;
this.bmId = data.bm_id;
data.composition.ligands.forEach(x => this.residues.push(new Residue(x, true)));
this.tmpResidueSet = new ObjectSet<Residue>();
this.tmpNodesSet = new ObjectSet<InteractionNode>();
data.composition.ligands.forEach(x => this.tmpResidueSet.tryAdd(new Residue(x, true)));
data.interactions.forEach(x => {
let beginingNode = this.processResidueInteractionPartner(x.begin);
let bgnNode = this.processResidueInteractionPartner(x.begin);
let endNode = this.processResidueInteractionPartner(x.end);
let link = new ResidueResidueLink(beginingNode, endNode, x.interactions)
let link = new ResidueResidueLink(bgnNode, endNode, x.interactions)
this.links.push(link);
});
this.interactionNodes = Array.from(this.tmpNodesSet.values());
return this;
}
private processResidueInteractionPartner(r: any): InteractionNode {
let residue = this.residues.find(x => {
let hash = `${r.chain_id}${r.author_residue_number}${(r.author_insertion_code === ' ' ? '' : r.author_insertion_code)}`;;
return hash === x.id;
});
if (residue === undefined) {
residue = new Residue(r, false);
this.residues.push(residue);
}
let node = this.interactionNodes.find(x => x.id === residue.id);
if (node === undefined) {
let node = new InteractionNode(residue, 1.0, residue.id);
this.interactionNodes.push(node);
return node;
}
let residue = new Residue(r, false);
residue = this.tmpResidueSet.tryAdd(residue);
let node = new InteractionNode(residue, 1.0, residue.id);
node = this.tmpNodesSet.tryAdd(node);
return node;
}
......@@ -403,15 +380,12 @@ namespace Model {
else scale = 1.0
let center = d.getCenter(atom_names);
let id = `${r.id}_${atom_names.reduce((x, y) => x + "_" + y)}`;
let nodeProvider = this.interactionNodes.find(x => x.id === id);
let id = `${r.id}_${atom_names.sort().reduce((x, y) => x + "_" + y)}`;
if (nodeProvider === undefined) {
let tmpNode = new InteractionNode(r, scale, id, center.x, center.y);
this.interactionNodes.push(tmpNode);
return tmpNode;
}
return nodeProvider;
let node = new InteractionNode(r, scale, id, center.x, center.y);
node = this.tmpNodesSet.tryAdd(node);
return node;
}
......@@ -419,23 +393,77 @@ namespace Model {
this.pdbId = pdbId;
this.bmId = `${data.ligand.chem_comp_id}_${data.ligand.chain_id}_${data.ligand.author_residue_number}`;
this.tmpResidueSet = new ObjectSet<Residue>();
this.tmpNodesSet = new ObjectSet<InteractionNode>();
let tmpLinks = new Array<LigandResidueLink>();
let ligandResidue = new Residue(data.ligand, true);
this.residues.push(ligandResidue);
this.tmpResidueSet.tryAdd(ligandResidue);
ligand.atoms.forEach(x => this.processLigandInteractionPartner(ligandResidue, ligand, [x.name]));
data.interactions.forEach(x => {
let beginingNode = this.processLigandInteractionPartner(ligandResidue, ligand, x.ligand_atoms);
let bgnNode = this.processLigandInteractionPartner(ligandResidue, ligand, x.ligand_atoms);
let endNode = this.processResidueInteractionPartner(x.end);
let tmpLink = this.links.find(x => (<LigandResidueLink>x).containsBothNodes(beginingNode, endNode)) as LigandResidueLink;
if (bgnNode.residue.equals(endNode.residue)) {
this.tmpNodesSet.delete(endNode);
return; // we do not want to show 'self interactions'
}
let tmpLink = tmpLinks.find(x => (<LigandResidueLink>x).containsBothNodes(bgnNode, endNode)) as LigandResidueLink;
if (tmpLink !== undefined) {
tmpLink.addInteraction(x.ligand_atoms, x.end.atom_names, x.interaction_type, x.interaction_details, x.distance);
} else {
let link = new LigandResidueLink(beginingNode, endNode, x.ligand_atoms, x.end.atom_names, x.interaction_type, x.interaction_details, x.distance);
this.links.push(link);
let link = new LigandResidueLink(bgnNode, endNode, x.ligand_atoms, x.end.atom_names, x.interaction_type, x.interaction_details, x.distance);
tmpLinks.push(link);
}
});
this.interactionNodes = Array.from(this.tmpNodesSet);
this.links = this.filterOutAromaticAtomAtomInteractions(tmpLinks);
return this;
}
/**
* Filter out Aromatic atom atom interactions provided there is
* an evidence of the same atom being part of the stacking information
*
* This is for clarity purposes only.
*
* @private
* @param {Array<LigandResidueLink>} src
* @returns {Array<LigandResidueLink>}
* @memberof BindingSite
*/
private filterOutAromaticAtomAtomInteractions(src: Array<LigandResidueLink>): Array<LigandResidueLink> {
let result = new Array<LigandResidueLink>();
src.forEach((x: LigandResidueLink) => {
let isAtomAtom = x.interaction.map(y => y.interactionType).every(z => z === InteractionType.AtomAtom);
if (x.getLinkClass() === 'aromatic' && isAtomAtom) {
let targetAtoms = x.interaction.map(y => y.targetAtoms).reduce((a, b) => a.concat(b));
let otherInteractions = new Set(src.filter(y => y.target.equals(x.target)));
otherInteractions.delete(x);
let otherBoundAtoms = Array.from(otherInteractions)
.map(y => y.interaction)
.reduce((a, b) => a.concat(b))
.map(y => y.targetAtoms)
.reduce((a, b) => a.concat(b));
if (otherBoundAtoms.includes(targetAtoms[0])) {
return;
}
}
result.push(x);
});
return result;
}
}
}
\ No newline at end of file
/**
* Extension class just to get objects and collections work nicely.
* Not sure what the javascript way is if you need distinct on
*
* @export
* @class ObjectSet
* @extends {Set<T>}
* @template T
*/
class ObjectSet<T> extends Set<T> {
public tryAdd(value: T): T {
let item = undefined;
this.forEach((x: any) => {
if (x.equals(value)) {
item = x;
return;
}
});
if (item === undefined) {
item = value;
super.add(value);
}
return item;
}
}
\ No newline at end of file
/**
* Singleton instance to provide residue abbreviations to the component
*
* @class ResidueProvider
*/
class ResidueProvider {
private static instance: ResidueProvider;
private mapping: Map<string, string>;
public downloadPromises: Array<Promise<any>>;
/**
* The Singleton's constructor should always be private to prevent direct
* construction calls with the `new` operator.
* @memberof ResidueProvider
*/
private constructor() {
this.mapping = new Map<string, string>(Config.aaAbreviations);
this.downloadPromises = new Array<Promise<any>>();
}
/**
* The static method that controls the access to the singleton instance.
*
* This implementation let you subclass the Singleton class while keeping
* just one instance of each subclass around.
*
* @static
* @returns {ResidueProvider}
* @memberof ResidueProvider
*/
public static getInstance(): ResidueProvider {
if (!ResidueProvider.instance) {
ResidueProvider.instance = new ResidueProvider();
}
return ResidueProvider.instance;
}
/**
* Get single letter abbreviation of the ligand.
*
* @param {string} name Name of the chemical compound
* @returns Single letter abbreviation of the ligand
* @memberof ResidueProvider
*/
public getAminoAcidAbbreviation(name: string): string {
return this.mapping.has(name) ? this.mapping.get(name) : undefined;
}
/**
* Fetch single letter abbreviations from API if they are not present
*
* @param {string} n
* @returns
* @memberof ResidueProvider
*/
public downloadAnnotation(r: Model.Residue): void {
if (this.mapping.has(r.chemCompId) || r.isLigand) return;
let url = Resources.residueTypeAPI(r.chemCompId);
let res = d3.json(url).then(x => {
let code = x[r.chemCompId][0].one_letter_code;
this.mapping.set(r.chemCompId, code);
return code;
});
this.downloadPromises.push(res);
}
}
\ No newline at end of file
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment