Commit 329914a0 authored by Lukas Pravda's avatar Lukas Pravda
Browse files

Merge branch 'dev' -- release 1.0

parents 8dbb3078 f701abb1
Pipeline #176751 passed with stages
in 40 seconds
## RELEASE 1.0 - 28 April 2021
* Support to display atom names
* Use `pdbeccdutils=0.6` data
* Minor bug fixes
## RELEASE 0.2 - 24 February 2020 ## RELEASE 0.2 - 24 February 2020
Component rembranded to `pdb-ligand-env` Component rebranded to `pdb-ligand-env`
### Features ### Features
......
# PDB ligand environment component # 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. 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, atom names, and binding site interactions.
## Step after cloning (use local server to see demo pages) ## Step after cloning (use local server to see demo pages)
...@@ -133,6 +133,8 @@ let uiParams = { ...@@ -133,6 +133,8 @@ let uiParams = {
help: false, // allow help option from the component menu help: false, // allow help option from the component menu
residueLabel: true, // show residue label residueLabel: true, // show residue label
tooltip: true // show residue tooltip on mouse hover tooltip: true // show residue tooltip on mouse hover
menu: true // allow menu to be (not) available
names: true // allow ligand depiction with atom names
}; };
this.display = new Visualization(this, uiParams, environment); this.display = new Visualization(this, uiParams, environment);
...@@ -144,8 +146,8 @@ this.display.initCarbohydratePolymerInteractions('5e98', 'bm1','3'); ...@@ -144,8 +146,8 @@ this.display.initCarbohydratePolymerInteractions('5e98', 'bm1','3');
// to display ligand interactions // to display ligand interactions
this.display.initLigandInteractions('1cbs', 200, 'A'); this.display.initLigandInteractions('1cbs', 200, 'A');
// to display chemical component only // to display chemical component with atom names only
this.display.initLigandDisplay('HEM'); this.display.initLigandDisplay('HEM', true);
```` ````
## Parameters ## Parameters
...@@ -160,4 +162,5 @@ this.display.initLigandDisplay('HEM'); ...@@ -160,4 +162,5 @@ this.display.initLigandDisplay('HEM');
| substructure | string[] | No | List of atom names to be highlighted on the ligand structure | | 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)` | | color | string | No | HEX representation of the color highlight. `(Default: #D3D3D3)` |
| zoom-on | boolean | No | Allow zoom functionality on the component level. | | zoom-on | boolean | No | Allow zoom functionality on the component level. |
| names-on | boolean | No | Allow ligand depiction to be displayed with atom names. |
| environment | string | No | What data should be used: one of `production`, `development`, `internal` or a shorthand `prod`, `dev`, `int`. | | environment | string | No | What data should be used: one of `production`, `development`, `internal` or a shorthand `prod`, `dev`, `int`. |
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
<script src="https://cdn.jsdelivr.net/npm/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js" <script src="https://cdn.jsdelivr.net/npm/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"
charset="utf-8"></script> charset="utf-8"></script>
<!--PDBe interactions component--> <!--PDBe interactions component-->
<script type="module" src="http://127.0.0.1:8080/pdb-ligand-env-component-0.3.0-min.js"></script> <script type="module" src="http://127.0.0.1:8080/pdb-ligand-env-component-1.0.0-min.js"></script>
</head> </head>
...@@ -37,7 +37,7 @@ ...@@ -37,7 +37,7 @@
<!--Mode C--> <!--Mode C-->
<div style="position: relative; float: left;"> <div style="position: relative; float: left;">
<div style="width: 500px; height: 500px; position: relative"> <div style="width: 500px; height: 500px; position: relative">
<pdb-ligand-env pdb-res-name="CLR" zoom-on></pdb-ligand-env> <pdb-ligand-env pdb-res-name="CLR" zoom-on names-on></pdb-ligand-env>
</div> </div>
</div> </div>
......
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
<script src="https://cdn.jsdelivr.net/npm/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js" <script src="https://cdn.jsdelivr.net/npm/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"
charset="utf-8"></script> charset="utf-8"></script>
<script type="module" src="pdb-ligand-env-component-0.3.0-min.js"></script> <script type="module" src="pdb-ligand-env-component-1.0.0-min.js"></script>
<script> <script>
var renderBmInteractions = function (id, bmId) { var renderBmInteractions = function (id, bmId) {
var int = var int =
...@@ -126,9 +126,9 @@ ...@@ -126,9 +126,9 @@
<!-- <!--
Further use in the app for bound molecule interactions: Further use in the app for bound molecule interactions:
<pdb-ligand-env pdb-id="3d12" bound-molecule-id="bm1"></pdb-ligand-env> <pdb-ligand-env pdb-id="3d12" bound-molecule-id="bm1"></pdb-ligand-env>
for ligand interactions: for ligand interactions:
<pdb-ligand-env pdb-id="3d12" pdb-chain-id="A" pdb-res-id="200"></pdb-ligand-env> <pdb-ligand-env pdb-id="3d12" pdb-chain-id="A" pdb-res-id="200"></pdb-ligand-env>
--> -->
......
This diff is collapsed.
{ {
"name": "pdb-ligand-env", "name": "pdb-ligand-env",
"version": "0.3.0", "version": "1.0.0",
"description": "", "description": "",
"main": "app.js", "main": "app.js",
"dependencies": { "dependencies": {
......
...@@ -17,6 +17,7 @@ class pdbLigandEnv extends LitElement { ...@@ -17,6 +17,7 @@ class pdbLigandEnv extends LitElement {
substructureHighlight: { type: Array, attribute: 'substructure' }, substructureHighlight: { type: Array, attribute: 'substructure' },
substructureColor: { type: String, attribute: 'color' }, substructureColor: { type: String, attribute: 'color' },
zoomOn: { type: Boolean, attribute: 'zoom-on' }, zoomOn: { type: Boolean, attribute: 'zoom-on' },
namesOn: { type: Boolean, attribute: 'names-on' },
env: { type: String, attribute: 'environment' }, env: { type: String, attribute: 'environment' },
}; };
} }
...@@ -32,9 +33,9 @@ class pdbLigandEnv extends LitElement { ...@@ -32,9 +33,9 @@ class pdbLigandEnv extends LitElement {
uiParams.menu = this.pdbId !== undefined; uiParams.menu = this.pdbId !== undefined;
let env = this.env === undefined ? "production" : this.env; let env = this.env === undefined ? "production" : this.env;
let names = this.namesOn === undefined ? false : this.namesOn;
this.display = new Visualization(this, uiParams, env); this.display = new Visualization(this, uiParams, env);
if (this.pdbId) { if (this.pdbId) {
if (this.entityId) { if (this.entityId) {
this.display.initCarbohydratePolymerInteractions(this.pdbId, this.bmId, this.entityId); this.display.initCarbohydratePolymerInteractions(this.pdbId, this.bmId, this.entityId);
...@@ -47,7 +48,7 @@ class pdbLigandEnv extends LitElement { ...@@ -47,7 +48,7 @@ class pdbLigandEnv extends LitElement {
} }
} }
else if (this.resName) { else if (this.resName) {
this.display.initLigandDisplay(this.resName).then(() => this.display.centerScene()); this.display.initLigandDisplay(this.resName, names).then(() => this.display.centerScene());
} }
} }
...@@ -84,7 +85,11 @@ class pdbLigandEnv extends LitElement { ...@@ -84,7 +85,11 @@ class pdbLigandEnv extends LitElement {
} }
set zoom(data) { set zoom(data) {
if (this.display !== undefined) this.display.toogleZoom(data); if (this.display !== undefined) this.display.toggleZoom(data);
}
set atomNames(data) {
this.display.toggleDepiction(data);
} }
//#endregion properties //#endregion properties
......
...@@ -70,6 +70,7 @@ namespace Config { ...@@ -70,6 +70,7 @@ namespace Config {
residueLabel: boolean; residueLabel: boolean;
tooltip: boolean; tooltip: boolean;
menu: boolean; menu: boolean;
names: boolean;
constructor() { constructor() {
this.reinitialize = true; this.reinitialize = true;
...@@ -82,6 +83,7 @@ namespace Config { ...@@ -82,6 +83,7 @@ namespace Config {
this.residueLabel = true; this.residueLabel = true;
this.tooltip = true; this.tooltip = true;
this.menu = true; this.menu = true;
this.names = true;
} }
} }
} }
\ No newline at end of file
...@@ -8,10 +8,10 @@ ...@@ -8,10 +8,10 @@
* @class Depiction * @class Depiction
* @param {string} ccdId PDB CCD id. * @param {string} ccdId PDB CCD id.
* @param {Atom[]} atoms List of atoms. * @param {Atom[]} atoms List of atoms.
* @param {Bond[]} bonds Visual representation of bonds. * @param {Bond[]} bonds Visual representation of bonds.
* They do not correlate 1:1 with a number of bonds! * They do not correlate 1:1 with a number of bonds!
* @param {Vector2D} resolution x,y dimension of the image. Needs to be used * @param {Vector2D} resolution x,y dimension of the image. Needs to be used
* for a scene shift, so it is centered. * for a scene shift, so it is centered.
*/ */
class Depiction { class Depiction {
ccdId: string; ccdId: string;
...@@ -39,7 +39,7 @@ class Depiction { ...@@ -39,7 +39,7 @@ class Depiction {
this.bonds = new Array<Bond>(); this.bonds = new Array<Bond>();
let bds = new Set<string>(); let bds = new Set<string>();
data.bonds.forEach(x => { data.bonds.forEach(x => {
let atomA = this.atoms.find(e => e.name == x.bgn); let atomA = this.atoms.find(e => e.name == x.bgn);
let atomB = this.atoms.find(e => e.name == x.end); let atomB = this.atoms.find(e => e.name == x.end);
...@@ -58,14 +58,14 @@ class Depiction { ...@@ -58,14 +58,14 @@ class Depiction {
/** /**
* Returns an initial position of Residue node bound to a list of * Returns an initial position of Residue node bound to a list of
* atom. * atom.
* *
* Present implementation sorts all the partners based on the atom * Present implementation sorts all the partners based on the atom
* degree and then gets the one with the lovest degree and places * degree and then gets the one with the lovest degree and places
* the initial residue position along the vector pointing from it. * the initial residue position along the vector pointing from it.
* *
* @param {string[]} atomNames list of atom names the bound residue * @param {string[]} atomNames list of atom names the bound residue
* has a contact with. * has a contact with.
* @returns {Vector2D} Returns an initial placement of the residue in contact. * @returns {Vector2D} Returns an initial placement of the residue in contact.
* @memberof Depiction * @memberof Depiction
...@@ -90,11 +90,13 @@ class Depiction { ...@@ -90,11 +90,13 @@ class Depiction {
return new Vector2D(x, y); return new Vector2D(x, y);
} }
public draw() { public draw(atomNames: boolean = false) {
this.structure.selectAll("*").remove(); this.structure.selectAll("*").remove();
this.appendBondVisuals(); this.appendBondVisuals();
this.appendTexts();
if (atomNames) this.appendAtomNames();
else this.appendLabels();
} }
public highlightSubgraph(atoms: Array<string>, color: string = undefined) { public highlightSubgraph(atoms: Array<string>, color: string = undefined) {
...@@ -145,39 +147,51 @@ class Depiction { ...@@ -145,39 +147,51 @@ class Depiction {
.attr('d', (y: Bond) => y.coords); .attr('d', (y: Bond) => y.coords);
} }
/**
* Append atom name labels to the visualization.
*
* @memberof Depiction
*/
private appendAtomNames() {
this.structure.selectAll()
.data(this.atoms)
.enter()
.append('text')
.attr('filter', "url(#solid-background)")
.attr('style', 'font-size:21px;font-style:normal;font-weight:normal;fill-opacity:1;stroke:none;font-family:sans-serif;fill:#000000')
.attr('x', x => x.position.x)
.attr('y', x => x.position.y)
.attr('dominant-baseline', 'central')
.attr('text-anchor', 'middle')
.text(x => x.name);
}
/** /**
* Append depiction labels to the visualization. Because RDKIt places * Append depiction labels to the visualization. Because RDKIt places
* the labels slightly differently this information needs to be * the labels slightly differently this information needs to be
* consumed too, because we cannot use just atom position directly. * consumed too, because we cannot use just atom position directly.
* Also there are all sorts of colorful subscripts and superscripts, * Also there are all sorts of colorful subscripts and superscripts,
* so it is much easier to use it this way. * so it is much easier to use it this way.
* *
* @memberof Depiction * @memberof Depiction
*/ */
private appendTexts(): void { private appendLabels() {
let data = this.atoms let data = this.atoms
.filter(x => x.labels.length > 0) .filter(x => x.labels.length > 0);
?.map(x => x.labels)
?.reduce((a, b) => a.concat(b));
this.structure.selectAll() this.structure.selectAll()
.data(data) .data(data)
.enter() .enter()
.append('text') .append('g')
.attr('filter', "url(#solid-background)") .attr('filter', 'url(#solid-background)')
.attr('style', (x: any) => x.style)
.attr('x', (x: any) => x.x)
.attr('y', (x: any) => x.y)
.attr('dominant-baseline', (x: any) => x['dominant-baseline'])
.attr('text-anchor', (x: any) => x['text-anchor'])
.each(function (x: any) { .each(function (x: any) {
for (var i = 0; i < x.labels.length; i++) {
for (var i = 0; i < x.tspans.length; i++) {
d3.select(this) d3.select(this)
.append('tspan') .append('path')
.attr('style', x.tspans[i].style) .attr('d', x.labels[i].d)
.text(x.tspans[i].value); .style("background-color", "white")
.attr('fill', x.labels[i].fill)
} }
}); });
} }
...@@ -243,7 +257,7 @@ class Atom { ...@@ -243,7 +257,7 @@ class Atom {
} }
/** /**
* *
* *
* @param {Atom} other * @param {Atom} other
* @returns true if the atoms are equal * @returns true if the atoms are equal
...@@ -310,7 +324,7 @@ class Vector2D { ...@@ -310,7 +324,7 @@ class Vector2D {
/** /**
* Composes vectors to a single one. This is used in infering the * Composes vectors to a single one. This is used in infering the
* original placement of the residue nodes. * original placement of the residue nodes.
* *
* @static * @static
...@@ -359,7 +373,7 @@ class Bond { ...@@ -359,7 +373,7 @@ class Bond {
/** /**
* Get the other atom for a given bond. * Get the other atom for a given bond.
* *
* @param {Atom} other * @param {Atom} other
* @returns {Atom} The other atom from the bond. * @returns {Atom} The other atom from the bond.
* @throws {Error} if the atom is not part of that bond at all. * @throws {Error} if the atom is not part of that bond at all.
* @memberof Bond * @memberof Bond
......
...@@ -17,7 +17,7 @@ class Visualization { ...@@ -17,7 +17,7 @@ class Visualization {
private links: any; private links: any;
// #endregion // #endregion
// #region data properties // #region data properties
private environment: Model.Environment; private environment: Model.Environment;
private pdbId: string; private pdbId: string;
private bindingSites: Model.BindingSite[]; private bindingSites: Model.BindingSite[];
...@@ -41,8 +41,9 @@ class Visualization { ...@@ -41,8 +41,9 @@ class Visualization {
this.visualsMapper = new VisualsMapper(this.environment); this.visualsMapper = new VisualsMapper(this.environment);
this.rProvider = ResidueProvider.getInstance(this.environment); this.rProvider = ResidueProvider.getInstance(this.environment);
this.fullScreen = false;
this.bindingSites = new Array<Model.BindingSite>(); this.bindingSites = new Array<Model.BindingSite>();
this.fullScreen = false;
this.nodeDragged = false; this.nodeDragged = false;
if (uiParameters === undefined) uiParameters = new Config.UIParameters(); if (uiParameters === undefined) uiParameters = new Config.UIParameters();
...@@ -126,7 +127,6 @@ class Visualization { ...@@ -126,7 +127,6 @@ class Visualization {
}); });
this.links?.attr('opacity', 1); this.links?.attr('opacity', 1);
this.nodes?.attr('opacity', 1); this.nodes?.attr('opacity', 1);
} }
private linkMouseOverEventHandler(x: Model.Link, i: number, g: any) { private linkMouseOverEventHandler(x: Model.Link, i: number, g: any) {
...@@ -190,7 +190,7 @@ class Visualization { ...@@ -190,7 +190,7 @@ class Visualization {
/** /**
* Download bound molecule interactions data from PDBe Graph API end point * Download bound molecule interactions data from PDBe Graph API end point
* /pdb/bound_molecule_interactions * /pdb/bound_molecule_interactions
* *
* Correct parameters can be obtained using API call: * Correct parameters can be obtained using API call:
* /pdb/bound_molecules * /pdb/bound_molecules
* *
...@@ -212,7 +212,7 @@ class Visualization { ...@@ -212,7 +212,7 @@ class Visualization {
/** /**
* Download carbohydrate interactions data from PDBe Graph API end point * Download carbohydrate interactions data from PDBe Graph API end point
* /pdb/carbohydrate_polymer_interactions * /pdb/carbohydrate_polymer_interactions
* *
* Correct parameters can be obtained using API call: * Correct parameters can be obtained using API call:
* /pdb/bound_molecules * /pdb/bound_molecules
* *
...@@ -235,40 +235,40 @@ class Visualization { ...@@ -235,40 +235,40 @@ class Visualization {
/** /**
* Download ligand interactions data from PDBe Graph API end point * Download ligand interactions data from PDBe Graph API end point
* /pdb/bound_ligand_interactions. * /pdb/bound_ligand_interactions.
* *
* Correct parameters can be obtained using API call: * Correct parameters can be obtained using API call:
* /pdb/bound_molecules * /pdb/bound_molecules
* *
* @param {string} pdbId pdb id * @param {string} pdbId pdb id
* @param {number} resId residue number aka: auth_seq_id * @param {number} resId residue number aka: auth_seq_id
* @param {string} chainId chain id aka: auth_asym_id * @param {string} chainId chain id aka: auth_asym_id
* @memberof Visualization * @memberof Visualization
*/ */
public initLigandInteractions(pdbId: string, resId: number, chainId: string) { public initLigandInteractions(pdbId: string, resId: number, chainId: string, withNames: boolean = false) {
this.pdbId = pdbId; this.pdbId = pdbId;
let url = Resources.ligandInteractionsAPI(pdbId, chainId, resId, this.environment); let url = Resources.ligandInteractionsAPI(pdbId, chainId, resId, this.environment);
d3.json(url) d3.json(url)
.catch(e => this.processError(e, 'No interactions data are available.')) .catch(e => this.processError(e, 'No interactions data are available.'))
.then((data: any) => this.addLigandInteractions(data)) .then((data: any) => this.addLigandInteractions(data, withNames))
.then(() => new Promise(resolve => setTimeout(resolve, 1500))) .then(() => new Promise(resolve => setTimeout(resolve, 1500)))
.then(() => this.centerScene()); .then(() => this.centerScene());
} }
/** /**
* Download ligand structure given the anotation generated by the * Download ligand structure given the anotation generated by the
* PDBeChem process. * PDBeChem process.
* *
* @param {string} ligandId * @param {string} ligandId
* @returns * @returns
* @memberof Visualization * @memberof Visualization
*/ */
public async initLigandDisplay(ligandId: string) { public async initLigandDisplay(ligandId: string, withNames: boolean = false) {
const ligandUrl = Resources.ligandAnnotationAPI(ligandId, this.environment); const ligandUrl = Resources.ligandAnnotationAPI(ligandId, this.environment);
return d3.json(ligandUrl) return d3.json(ligandUrl)
.catch(e => this.processError(e, `Component ${ligandId} was not found.`)) .catch(e => this.processError(e, `Component ${ligandId} was not found.`))
.then((d: any) => this.addDepiction(d)) .then((d: any) => this.addDepiction(d, withNames))
.then(() => this.centerScene()); .then(() => this.centerScene());
} }
...@@ -280,9 +280,26 @@ class Visualization { ...@@ -280,9 +280,26 @@ class Visualization {
* the PDBeChem process. * the PDBeChem process.
* @memberof Visualization * @memberof Visualization
*/ */
public addDepiction(depiction: any) { public addDepiction(depiction: any, withNames: boolean) {
this.depiction = new Depiction(this.depictionRoot, depiction); this.depiction = new Depiction(this.depictionRoot, depiction);
this.depiction.draw(); this.depiction.draw(withNames);
}
/**
* Show depiction with/without atom names
*
* @param {boolean} withNames Controls atom labels to be displayed
* @memberof Visualization
*/
public toggleDepiction(withNames: boolean) {
if (!this.depiction) return;
this.depiction.draw(withNames);
}
public toggleZoom(active: boolean) {
this.zoomHandler = active ? this.getZoomHandler() : undefined;
} }
...@@ -310,25 +327,20 @@ class Visualization { ...@@ -310,25 +327,20 @@ class Visualization {
} }