Skip to content
Snippets Groups Projects
Commit ffc81010 authored by Lukas Pravda's avatar Lukas Pravda
Browse files

Merge branch 'development'

parents d5390436 b3e2116c
No related branches found
No related tags found
No related merge requests found
Pipeline #15244 passed with stages
in 21 seconds
build/3d12.json 100644 → 100755
+ 20718
27289
View file @ ffc81010
This diff is collapsed.
......@@ -318,11 +318,11 @@ declare namespace Visual {
* depictions and a file providing mapping between glycan class and het
* codes.
*
* @class GlycanMapper
* @class VisualsMapper
*/
declare class GlycanMapper {
images: Map<string, SVGElement>;
mapping: Map<string, string>;
declare class VisualsMapper {
glycanImages: Map<string, SVGElement>;
glycanMapping: Map<string, string>;
graphicsPromise: Promise<void>;
mappingPromise: Promise<void>;
constructor();
......@@ -343,7 +343,7 @@ declare class GlycanMapper {
* @returns {Promise} To check later if it has been processed.
* @memberof GlycanMapper
*/
private parseGlycanSymbols;
private parseSymbols;
/**
* Parses an external JSON configuration file which provides a mapping
* between glycan id (e.g. GlcA) and het codes.
......@@ -552,20 +552,21 @@ declare class Visualization {
private residueLabel;
private tooltip;
private pdbId;
private glycanMapper;
private visualsMapper;
private selectedBindingSite;
private zoom_handler;
private drag_handler;
constructor(element: string);
initialise(pdbid: string, i: number): void;
initialise(pdbid: string, i: number): Promise<void>;
private saveSvg;
private downloadInteractionsData;
private reinitialize;
private resize;
private centerScene;
private linkMouseOver;
private atomLinkMouseOver;
private nodeMouseOver;
private nodeMouseOut;
private resize;
private generateResidueLinkTooltip;
private generateAtomLinkTooltip;
private generateNodeTooltip;
......@@ -573,5 +574,5 @@ declare class Visualization {
private selectLigand;
private setupScene;
private simulationStep;
private centerScene;
private addSvgDefs;
}
......@@ -527,13 +527,13 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
* depictions and a file providing mapping between glycan class and het
* codes.
*
* @class GlycanMapper
* @class VisualsMapper
*/
class GlycanMapper {
class VisualsMapper {
constructor() {
this.mapping = new Map();
this.images = new Map();
this.graphicsPromise = this.parseGlycanSymbols('glycans.xml');
this.glycanMapping = new Map();
this.glycanImages = new Map();
this.graphicsPromise = this.parseSymbols('visuals.xml');
this.mappingPromise = this.parseGlycanMapping('het_mapping.json');
}
/**
......@@ -544,7 +544,7 @@ class GlycanMapper {
* @memberof GlycanMapper
*/
getGlycanImage(compId) {
return this.mapping.has(compId) ? this.images.get(this.mapping.get(compId)) : new SVGElement();
return this.glycanMapping.has(compId) ? this.glycanImages.get(this.glycanMapping.get(compId)) : new SVGElement();
}
/**
* Parse external file with glycan representation and stores it for
......@@ -555,12 +555,12 @@ class GlycanMapper {
* @returns {Promise} To check later if it has been processed.
* @memberof GlycanMapper
*/
parseGlycanSymbols(symbolsLink) {
parseSymbols(symbolsLink) {
return __awaiter(this, void 0, void 0, function* () {
return d3.xml(symbolsLink).then(x => {
let parsedImages = x.documentElement.getElementsByTagName('g');
let parsedImages = x.documentElement.getElementsByTagName('glycans')[0].getElementsByTagName('g');
for (let img of parsedImages) {
this.images.set(img.getAttribute('name'), img.outerHTML);
this.glycanImages.set(img.getAttribute('name'), img.outerHTML);
}
});
});
......@@ -578,7 +578,7 @@ class GlycanMapper {
parseGlycanMapping(mappingLink) {
return __awaiter(this, void 0, void 0, function* () {
return d3.json(mappingLink).then((i) => {
i.forEach(glycan => glycan['het_codes'].forEach(x => this.mapping.set(x, glycan['name'])));
i.forEach(glycan => glycan['het_codes'].forEach(x => this.glycanMapping.set(x, glycan['name'])));
});
});
}
......@@ -851,10 +851,10 @@ class BondVisual {
this.style = item.style.replace("stroke-width:2px", "stroke-width:4px");
}
}
/// <reference path="./glycans.ts" />
/// <reference path="./visualsMapping.ts" />
/// <reference path="./depictions.ts" />
class Visualization {
// #endregion
// #endregion handlers
constructor(element) {
// #endregion
// #region handlers
......@@ -878,6 +878,7 @@ class Visualization {
x.fx = d3.event.x;
x.fy = d3.event.y;
});
this.visualsMapper = new VisualsMapper();
this.tooltip = d3.select('#int-tooltip');
this.screenshotBtn = d3.select('#int-screenshot-btn');
this.centerBtn = d3.select('#int-center-btn');
......@@ -895,21 +896,23 @@ class Visualization {
.attr('width', () => this.width)
.attr('height', () => this.height);
this.visualization = this.svg.append('g').attr('id', 'vis-root');
this.glycanMapper = new GlycanMapper();
}
initialise(pdbid, i) {
this.pdbId = pdbid;
this.screenshotBtn.on('click', () => this.saveSvg(this));
this.centerBtn.on('click', () => this.centerScene(this));
this.homeBtn.on('click', () => this.reinitialize(this));
this.downloadBtn.on('click', () => this.downloadInteractionsData(this));
d3.json(`${this.pdbId}.json`)
.catch(e => { throw e; })
.then((data) => {
this.selectedBindingSite = new Model.BindingSite(data.boundMolecules[i]);
this.setupScene();
return __awaiter(this, void 0, void 0, function* () {
this.pdbId = pdbid;
this.screenshotBtn.on('click', () => this.saveSvg(this));
this.centerBtn.on('click', () => this.centerScene(this));
this.homeBtn.on('click', () => this.reinitialize(this));
this.downloadBtn.on('click', () => this.downloadInteractionsData(this));
d3.json(`${this.pdbId}.json`)
.catch(e => { throw e; })
.then((data) => {
this.selectedBindingSite = new Model.BindingSite(data.boundMolecules[i]);
this.setupScene();
});
});
}
// #region menu functions
saveSvg(ctx) {
d3.text('svgstyles.css').then(x => {
let svgToDl = d3.select('#int-canvas');
......@@ -944,6 +947,51 @@ class Visualization {
.duration(200)
.style('opacity', 0);
}
resize(ctx, isBoundMolecule) {
ctx.width = document.getElementById('int-canvas').clientWidth;
ctx.height = window.innerHeight;
ctx.svg.attr('width', ctx.width).attr('height', ctx.height);
if (isBoundMolecule) {
ctx.simulation.force('center', d3.forceCenter(ctx.width / 2, ctx.height / 2))
.restart();
}
else {
ctx.simulation.restart();
}
ctx.zoom_handler(ctx.svg);
}
centerScene(ctx) {
if (ctx.nodes.length === 0)
return;
// Get the bounding box
let minX = d3.min(ctx.nodes.data().map((x) => x.x));
let minY = d3.min(ctx.nodes.data().map((x) => x.y));
let maxX = d3.max(ctx.nodes.data().map((x) => x.x));
let maxY = d3.max(ctx.nodes.data().map((x) => x.y));
// The width and the height of the graph
let molWidth = maxX - minX;
let molHeight = maxY - minY;
// how much larger the drawing area is than the width and the height
let widthRatio = ctx.width / molWidth;
let heightRatio = ctx.height / molHeight;
// we need to fit it in both directions, so we scale according to
// the direction in which we need to shrink the most
let minRatio = Math.min(widthRatio, heightRatio) * 0.8;
// the new dimensions of the molecule
let newMolWidth = molWidth * minRatio;
let newMolHeight = molHeight * minRatio;
// translate so that it's in the center of the window
let xTrans = -(minX) * minRatio + (ctx.width - newMolWidth) / 2;
let yTrans = -(minY) * minRatio + (ctx.height - newMolHeight) / 2;
// do the actual moving
ctx.visualization.attr('transform', `translate(${xTrans}, ${yTrans}) scale(${minRatio})`);
// tell the zoomer what we did so that next we zoom, it uses the
// transformation we entered here
ctx.zoom_handler.transform(ctx.svg, d3.zoomIdentity);
}
;
// #endregion menu functions
// #region mouse events
linkMouseOver(x) {
this.tooltip.transition()
.duration(200)
......@@ -973,26 +1021,13 @@ class Visualization {
if (x.residue.isLigand)
d3.select(g[i]).style('cursor', 'default');
d3.select(g[i])
.attr('transform', () => `translate(${x.x},${x.y}) scale(${x.scale})`);
.attr('transform', `translate(${x.x},${x.y}) scale(${x.scale})`);
this.tooltip.transition()
.duration(200)
.style('opacity', 0);
}
// #endregion mouse events
// #region Labels
resize(ctx, isBoundMolecule) {
ctx.width = document.getElementById('int-canvas').clientWidth;
ctx.height = window.innerHeight;
ctx.svg.attr('width', ctx.width).attr('height', ctx.height);
if (isBoundMolecule) {
ctx.simulation.force('center', d3.forceCenter(ctx.width / 2, ctx.height / 2))
.restart();
}
else {
ctx.simulation.restart();
}
ctx.centerScene(ctx);
ctx.zoom_handler(ctx.svg);
}
generateResidueLinkTooltip(x) {
return `<span>${x.source.toString()} - ${x.target.toString()} </span><span>${x.getTypes().join(', ')}.</span>`;
}
......@@ -1013,6 +1048,7 @@ class Visualization {
return __awaiter(this, void 0, void 0, function* () {
if (!n.residue.isLigand)
return;
this.addSvgDefs();
let context = this;
this.visualization.remove();
this.nodeMouseOut(n, i, g);
......@@ -1026,18 +1062,21 @@ class Visualization {
.append('g')
.attr('transform', `translate(${xShift}, ${yShift}) scale(1)`);
this.links = this.visualization.append('g')
.attr('id', 'links')
.selectAll()
.data(visuals.links)
.enter().append('line')
.attr('class', (e) => `svg-bond svg-bond-${e.getLinkClass()}`)
.attr('marker-end', (e) => `url(#arrow-${e.getLinkClass()})`)
.on('mouseover', this.atomLinkMouseOver.bind(context))
.on('mouseout', () => {
this.tooltip.transition()
.duration(500)
.style('opacity', 0);
});
depiction.appendBondVisualsTo(this.visualization);
depiction.appendTextsTo(this.visualization);
let depictionRoot = this.visualization.append('g').attr('id', 'depiction');
depiction.appendBondVisualsTo(depictionRoot);
depiction.appendTextsTo(depictionRoot);
visuals.nodes.forEach(x => {
if (!(x instanceof Visual.ResidueNode))
return;
......@@ -1048,6 +1087,7 @@ class Visualization {
});
//setup nodes
this.nodes = this.visualization.append('g')
.attr('id', 'nodes')
.selectAll()
.data(visuals.nodes)
.enter().append('g');
......@@ -1056,15 +1096,15 @@ class Visualization {
.on('mouseover', (x, i, g) => this.nodeMouseOver(x, i, g))
.on('mouseout', (x, i, g) => context.nodeMouseOut(x, i, g));
// draw glycans
yield this.glycanMapper.graphicsPromise;
yield this.glycanMapper.mappingPromise;
yield this.visualsMapper.graphicsPromise;
yield this.visualsMapper.mappingPromise;
this.nodes.filter(x => x instanceof Visual.ResidueNode)
.filter((x) => this.glycanMapper.mapping.has(x.residue.label_comp_id))
.html((e) => this.glycanMapper.getGlycanImage(e.residue.label_comp_id));
.filter((x) => this.visualsMapper.glycanMapping.has(x.residue.label_comp_id))
.html((e) => this.visualsMapper.getGlycanImage(e.residue.label_comp_id));
// draw rest
this.nodes
.filter(x => x instanceof Visual.ResidueNode)
.filter(x => !this.glycanMapper.mapping.has(x.residue.label_comp_id))
.filter(x => !this.visualsMapper.glycanMapping.has(x.residue.label_comp_id))
.append('circle')
.attr('r', '25');
this.nodes
......@@ -1105,10 +1145,12 @@ class Visualization {
let context = this;
let visuals = context.selectedBindingSite.getBoundMoleculeInteractions();
this.links = this.visualization.append('g')
.attr('id', 'links')
.selectAll()
.data(visuals.links)
.enter().append('line')
.attr('class', (e) => `svg-bond svg-bond-${e.getLinkClass()}`)
.attr('marker-end', (e) => `url(#arrow-${e.getLinkClass()})`)
.on('mouseover', this.linkMouseOver.bind(context))
.on('mouseout', () => {
this.tooltip.transition()
......@@ -1116,6 +1158,7 @@ class Visualization {
.style('opacity', 0);
});
this.nodes = this.visualization.append('g')
.attr('id', 'nodes')
.selectAll()
.data(visuals.nodes)
.enter().append('g')
......@@ -1127,11 +1170,11 @@ class Visualization {
let pivot = visuals.nodes.filter((x) => x.residue.isLigand)[0];
this.selectLigand(pivot, 0, this.nodes);
}
yield this.glycanMapper.graphicsPromise;
yield this.glycanMapper.mappingPromise;
this.nodes.filter((n) => this.glycanMapper.mapping.has(n.residue.label_comp_id))
.html((e) => this.glycanMapper.getGlycanImage(e.residue.label_comp_id));
this.nodes.filter((e) => !this.glycanMapper.mapping.has(e.residue.label_comp_id))
yield this.visualsMapper.graphicsPromise;
yield this.visualsMapper.mappingPromise;
this.nodes.filter((n) => this.visualsMapper.glycanMapping.has(n.residue.label_comp_id))
.html((e) => this.visualsMapper.getGlycanImage(e.residue.label_comp_id));
this.nodes.filter((e) => !this.visualsMapper.glycanMapping.has(e.residue.label_comp_id))
.append('circle')
.attr('r', '25');
this.nodes.append('text')
......@@ -1169,39 +1212,29 @@ class Visualization {
ctx.nodes.attr('transform', (d) => `translate(${d.x},${d.y}) scale(${d.scale})`);
ctx.links.attr('x1', (x) => x.source.x)
.attr('y1', (x) => x.source.y)
.attr('x2', (x) => x.target.x)
.attr('y2', (x) => x.target.y);
.attr('x2', (x) => x.source.x + 0.85 * (x.target.x - x.source.x))
.attr('y2', (x) => x.source.y + 0.85 * (x.target.y - x.source.y));
}
centerScene(ctx) {
if (ctx.nodes.length === 0)
return;
// Get the bounding box
let minX = d3.min(ctx.nodes.data().map((x) => x.x));
let minY = d3.min(ctx.nodes.data().map((x) => x.y));
let maxX = d3.max(ctx.nodes.data().map((x) => x.x));
let maxY = d3.max(ctx.nodes.data().map((x) => x.y));
// The width and the height of the graph
let molWidth = maxX - minX;
let molHeight = maxY - minY;
// how much larger the drawing area is than the width and the height
let widthRatio = ctx.width / molWidth;
let heightRatio = ctx.height / molHeight;
// we need to fit it in both directions, so we scale according to
// the direction in which we need to shrink the most
let min_ratio = Math.min(widthRatio, heightRatio) * 0.8;
// the new dimensions of the molecule
let newMolWidth = molWidth * min_ratio;
let newMolHeight = molHeight * min_ratio;
// translate so that it's in the center of the window
let x_trans = -(minX) * min_ratio + (ctx.width - newMolWidth) / 2;
let y_trans = -(minY) * min_ratio + (ctx.height - newMolHeight) / 2;
// do the actual moving
ctx.visualization.attr("transform", `translate(${x_trans}, ${y_trans}) scale(${min_ratio})`);
// tell the zoomer what we did so that next we zoom, it uses the
// transformation we entered here
ctx.zoom_handler(ctx.svg, x_trans, y_trans);
ctx.zoom_handler.scaleTo(ctx.svg, min_ratio);
addSvgDefs() {
let mapping = new Map([
["arrow-electrostatic", "#3F26BF"],
["arrow-vdw", "#9B7653"],
["arrow-metal", "#008080"],
["arrow-aromatic", "#AD4379"],
]);
d3.select('svg').append('defs');
mapping.forEach((x, y) => d3.select('defs').append('marker')
.attr('id', y)
.attr("markerWidth", 15)
.attr("markerHeight", 15)
.attr("refX", 7)
.attr("refY", 3)
.attr("orient", "auto")
.attr('markerUnits', 'strokeWidth')
.attr('viewBox', '0 0 20 20')
.append('path')
.attr('d', 'M0,0 L0,6 L9,3 z')
.attr('fill', x));
}
;
}
//# sourceMappingURL=app.js.map
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
......@@ -183,7 +183,7 @@
var pdbid = tokens[0] == "" ? '3d12' : tokens[0].substring(1)
var vis = new Visualization('body');
vis.initialise(pdbid, pdbid == '3d12' ? 2 : 0);
vis.initialise(pdbid, pdbid == '3d12' ? 1 : 0);
});
}());
</script>
......
#int-canvas {
cursor: default;
border: 1px solid black;
border-radius: 3px;
width: 100%;
height: 98.5vh;
......
This diff is collapsed.
/// <reference path="./glycans.ts" />
/// <reference path="./visualsMapping.ts" />
/// <reference path="./depictions.ts" />
class Visualization {
......@@ -27,7 +27,7 @@ class Visualization {
// #region data properties
private pdbId: string;
private glycanMapper: GlycanMapper;
private visualsMapper: VisualsMapper;
private selectedBindingSite: Model.BindingSite;
// #endregion
......@@ -52,9 +52,12 @@ class Visualization {
x.fx = d3.event.x;
x.fy = d3.event.y;
});
// #endregion
// #endregion handlers
constructor(element: string) {
this.visualsMapper = new VisualsMapper();
this.tooltip = d3.select('#int-tooltip');
this.screenshotBtn = d3.select('#int-screenshot-btn');
this.centerBtn = d3.select('#int-center-btn');
......@@ -72,14 +75,11 @@ class Visualization {
.append('svg')
.attr('xmlns', 'http://www.w3.org/2000/svg')
.attr('width', () => this.width)
.attr('height', () => this.height);
.attr('height', () => this.height)
this.visualization = this.svg.append('g').attr('id', 'vis-root');
this.glycanMapper = new GlycanMapper();
}
public initialise(pdbid: string, i: number) {
public async initialise(pdbid: string, i: number) {
this.pdbId = pdbid;
this.screenshotBtn.on('click', () => this.saveSvg(this));
......@@ -95,7 +95,7 @@ class Visualization {
});
}
// #region menu functions
private saveSvg(ctx: Visualization) {
d3.text('svgstyles.css').then(x => {
let svgToDl = d3.select('#int-canvas');
......@@ -138,9 +138,69 @@ class Visualization {
ctx.residueLabel.transition()
.duration(200)
.style('opacity', 0);
}
private resize(ctx: Visualization, isBoundMolecule: boolean) {
ctx.width = document.getElementById('int-canvas').clientWidth;
ctx.height = window.innerHeight;
ctx.svg.attr('width', ctx.width).attr('height', ctx.height);
if (isBoundMolecule) {
ctx.simulation.force('center', d3.forceCenter(ctx.width / 2, ctx.height / 2))
.restart();
} else {
ctx.simulation.restart();
}
ctx.zoom_handler(ctx.svg);
}
private centerScene(ctx: Visualization) {
if (ctx.nodes.length === 0)
return;
// Get the bounding box
let minX: any = d3.min(ctx.nodes.data().map((x) => x.x));
let minY: any = d3.min(ctx.nodes.data().map((x) => x.y));
let maxX: any = d3.max(ctx.nodes.data().map((x) => x.x));
let maxY: any = d3.max(ctx.nodes.data().map((x) => x.y));
// The width and the height of the graph
let molWidth = maxX - minX;
let molHeight = maxY - minY;
// how much larger the drawing area is than the width and the height
let widthRatio = ctx.width / molWidth;
let heightRatio = ctx.height / molHeight;
// we need to fit it in both directions, so we scale according to
// the direction in which we need to shrink the most
let minRatio = Math.min(widthRatio, heightRatio) * 0.8;
// the new dimensions of the molecule
let newMolWidth = molWidth * minRatio;
let newMolHeight = molHeight * minRatio;
// translate so that it's in the center of the window
let xTrans = -(minX) * minRatio + (ctx.width - newMolWidth) / 2;
let yTrans = -(minY) * minRatio + (ctx.height - newMolHeight) / 2;
// do the actual moving
ctx.visualization.attr('transform', `translate(${xTrans}, ${yTrans}) scale(${minRatio})`);
// tell the zoomer what we did so that next we zoom, it uses the
// transformation we entered here
ctx.zoom_handler.transform(ctx.svg, d3.zoomIdentity);
};
// #endregion menu functions
// #region mouse events
private linkMouseOver(x: Visual.Link) {
this.tooltip.transition()
.duration(200)
......@@ -180,33 +240,15 @@ class Visualization {
if (x.residue.isLigand) d3.select(g[i]).style('cursor', 'default');
d3.select(g[i])
.attr('transform', () => `translate(${x.x},${x.y}) scale(${x.scale})`);
.attr('transform', `translate(${x.x},${x.y}) scale(${x.scale})`);
this.tooltip.transition()
.duration(200)
.style('opacity', 0);
}
// #endregion mouse events
// #region Labels
private resize(ctx: Visualization, isBoundMolecule: boolean) {
ctx.width = document.getElementById('int-canvas').clientWidth;
ctx.height = window.innerHeight;
ctx.svg.attr('width', ctx.width).attr('height', ctx.height);
if (isBoundMolecule) {
ctx.simulation.force('center', d3.forceCenter(ctx.width / 2, ctx.height / 2))
.restart();
} else {
ctx.simulation.restart();
}
ctx.centerScene(ctx);
ctx.zoom_handler(ctx.svg);
}
private generateResidueLinkTooltip(x: Visual.Link): string {
return `<span>${x.source.toString()} - ${x.target.toString()} </span><span>${x.getTypes().join(', ')}.</span>`;
}
......@@ -232,6 +274,7 @@ class Visualization {
private async selectLigand(n: Visual.ResidueNode, i: number, g: any) {
if (!n.residue.isLigand) return;
this.addSvgDefs();
let context = this;
this.visualization.remove();
......@@ -248,13 +291,15 @@ class Visualization {
this.visualization = this.svg.append('g')
.attr('id', 'vis-root')
.append('g')
.attr('transform', `translate(${xShift}, ${yShift}) scale(1)`);
.attr('transform', `translate(${xShift}, ${yShift}) scale(1)`);
this.links = this.visualization.append('g')
.attr('id', 'links')
.selectAll()
.data(visuals.links)
.enter().append('line')
.attr('class', (e: Visual.AtomLink) => `svg-bond svg-bond-${e.getLinkClass()}`)
.attr('marker-end', (e: Visual.AtomLink) => `url(#arrow-${e.getLinkClass()})`)
.on('mouseover', this.atomLinkMouseOver.bind(context))
.on('mouseout', () => {
this.tooltip.transition()
......@@ -262,8 +307,9 @@ class Visualization {
.style('opacity', 0);
});
depiction.appendBondVisualsTo(this.visualization);
depiction.appendTextsTo(this.visualization);
let depictionRoot = this.visualization.append('g').attr('id', 'depiction');
depiction.appendBondVisualsTo(depictionRoot);
depiction.appendTextsTo(depictionRoot);
visuals.nodes.forEach(x => {
if (!(x instanceof Visual.ResidueNode)) return;
......@@ -278,6 +324,7 @@ class Visualization {
//setup nodes
this.nodes = this.visualization.append('g')
.attr('id', 'nodes')
.selectAll()
.data(visuals.nodes)
.enter().append('g');
......@@ -289,16 +336,16 @@ class Visualization {
// draw glycans
await this.glycanMapper.graphicsPromise;
await this.glycanMapper.mappingPromise;
await this.visualsMapper.graphicsPromise;
await this.visualsMapper.mappingPromise;
this.nodes.filter(x => x instanceof Visual.ResidueNode)
.filter((x: Visual.ResidueNode) => this.glycanMapper.mapping.has(x.residue.label_comp_id))
.html((e: Visual.ResidueNode) => this.glycanMapper.getGlycanImage(e.residue.label_comp_id));
.filter((x: Visual.ResidueNode) => this.visualsMapper.glycanMapping.has(x.residue.label_comp_id))
.html((e: Visual.ResidueNode) => this.visualsMapper.getGlycanImage(e.residue.label_comp_id));
// draw rest
this.nodes
.filter(x => x instanceof Visual.ResidueNode)
.filter(x => !this.glycanMapper.mapping.has(x.residue.label_comp_id))
.filter(x => !this.visualsMapper.glycanMapping.has(x.residue.label_comp_id))
.append('circle')
.attr('r', '25');
......@@ -340,16 +387,17 @@ class Visualization {
d3.select(window).on('resize', () => this.resize(context, false));
}
private async setupScene() {
let context = this;
let visuals = context.selectedBindingSite.getBoundMoleculeInteractions();
this.links = this.visualization.append('g')
.attr('id', 'links')
.selectAll()
.data(visuals.links)
.enter().append('line')
.attr('class', (e: Visual.Link) => `svg-bond svg-bond-${e.getLinkClass()}`)
.attr('marker-end', (e: Visual.Link) => `url(#arrow-${e.getLinkClass()})`)
.on('mouseover', this.linkMouseOver.bind(context))
.on('mouseout', () => {
this.tooltip.transition()
......@@ -358,6 +406,7 @@ class Visualization {
});
this.nodes = this.visualization.append('g')
.attr('id', 'nodes')
.selectAll()
.data(visuals.nodes)
.enter().append('g')
......@@ -372,14 +421,14 @@ class Visualization {
}
await this.glycanMapper.graphicsPromise;
await this.glycanMapper.mappingPromise;
await this.visualsMapper.graphicsPromise;
await this.visualsMapper.mappingPromise;
this.nodes.filter((n: Visual.ResidueNode) =>
this.glycanMapper.mapping.has(n.residue.label_comp_id))
.html((e: Visual.ResidueNode) => this.glycanMapper.getGlycanImage(e.residue.label_comp_id));
this.visualsMapper.glycanMapping.has(n.residue.label_comp_id))
.html((e: Visual.ResidueNode) => this.visualsMapper.getGlycanImage(e.residue.label_comp_id));
this.nodes.filter((e: Visual.ResidueNode) => !this.glycanMapper.mapping.has(e.residue.label_comp_id))
this.nodes.filter((e: Visual.ResidueNode) => !this.visualsMapper.glycanMapping.has(e.residue.label_comp_id))
.append('circle')
.attr('r', '25');
......@@ -423,51 +472,34 @@ class Visualization {
ctx.nodes.attr('transform', (d) => `translate(${d.x},${d.y}) scale(${d.scale})`);
ctx.links.attr('x1', (x: any) => x.source.x)
.attr('y1', (x: any) => x.source.y)
.attr('x2', (x: any) => x.target.x)
.attr('y2', (x: any) => x.target.y);
.attr('x2', (x: any) => x.source.x + 0.85 * (x.target.x - x.source.x))
.attr('y2', (x: any) => x.source.y + 0.85 * (x.target.y - x.source.y));
}
private centerScene(ctx: Visualization) {
if (ctx.nodes.length === 0)
return;
// Get the bounding box
let minX: any = d3.min(ctx.nodes.data().map((x) => x.x));
let minY: any = d3.min(ctx.nodes.data().map((x) => x.y));
let maxX: any = d3.max(ctx.nodes.data().map((x) => x.x));
let maxY: any = d3.max(ctx.nodes.data().map((x) => x.y));
// The width and the height of the graph
let molWidth = maxX - minX;
let molHeight = maxY - minY;
// how much larger the drawing area is than the width and the height
let widthRatio = ctx.width / molWidth;
let heightRatio = ctx.height / molHeight;
// we need to fit it in both directions, so we scale according to
// the direction in which we need to shrink the most
let min_ratio = Math.min(widthRatio, heightRatio) * 0.8;
// the new dimensions of the molecule
let newMolWidth = molWidth * min_ratio;
let newMolHeight = molHeight * min_ratio;
// translate so that it's in the center of the window
let x_trans = -(minX) * min_ratio + (ctx.width - newMolWidth) / 2;
let y_trans = -(minY) * min_ratio + (ctx.height - newMolHeight) / 2;
// do the actual moving
ctx.visualization.attr("transform", `translate(${x_trans}, ${y_trans}) scale(${min_ratio})`);
// tell the zoomer what we did so that next we zoom, it uses the
// transformation we entered here
ctx.zoom_handler(ctx.svg, x_trans, y_trans);
ctx.zoom_handler.scaleTo(ctx.svg, min_ratio);
};
private addSvgDefs() {
let mapping = new Map<string, string>([
["arrow-electrostatic", "#3F26BF"],
["arrow-vdw", "#9B7653"],
["arrow-metal", "#008080"],
["arrow-aromatic", "#AD4379"],
]);
d3.select('svg').append('defs');
mapping.forEach((x, y) =>
d3.select('defs').append('marker')
.attr('id', y)
.attr("markerWidth", 15)
.attr("markerHeight", 15)
.attr("refX", 7)
.attr("refY", 3)
.attr("orient", "auto")
.attr('markerUnits', 'strokeWidth')
.attr('viewBox', '0 0 20 20')
.append('path')
.attr('d', 'M0,0 L0,6 L9,3 z')
.attr('fill', x)
);
}
}
......@@ -4,21 +4,21 @@
* depictions and a file providing mapping between glycan class and het
* codes.
*
* @class GlycanMapper
* @class VisualsMapper
*/
class GlycanMapper {
class VisualsMapper {
public images: Map<string, SVGElement>;
public mapping: Map<string, string>;
public glycanImages: Map<string, SVGElement>;
public glycanMapping: Map<string, string>;
public graphicsPromise: Promise<void>;
public mappingPromise: Promise<void>;
constructor() {
this.mapping = new Map<string, string>();
this.images = new Map<string, SVGElement>();
this.glycanMapping = new Map<string, string>();
this.glycanImages = new Map<string, SVGElement>();
this.graphicsPromise = this.parseGlycanSymbols('glycans.xml');
this.graphicsPromise = this.parseSymbols('visuals.xml');
this.mappingPromise = this.parseGlycanMapping('het_mapping.json');
}
......@@ -30,7 +30,7 @@ class GlycanMapper {
* @memberof GlycanMapper
*/
public getGlycanImage(compId: string): SVGElement {
return this.mapping.has(compId) ? this.images.get(this.mapping.get(compId)) : new SVGElement();
return this.glycanMapping.has(compId) ? this.glycanImages.get(this.glycanMapping.get(compId)) : new SVGElement();
}
......@@ -44,12 +44,12 @@ class GlycanMapper {
* @returns {Promise} To check later if it has been processed.
* @memberof GlycanMapper
*/
private async parseGlycanSymbols(symbolsLink: string) {
private async parseSymbols(symbolsLink: string) {
return d3.xml(symbolsLink).then(x => {
let parsedImages: any = x.documentElement.getElementsByTagName('g');
let parsedImages: any = x.documentElement.getElementsByTagName('glycans')[0].getElementsByTagName('g');
for (let img of parsedImages) {
this.images.set(img.getAttribute('name'), img.outerHTML);
}
this.glycanImages.set(img.getAttribute('name'), img.outerHTML);
}
});
}
......@@ -64,10 +64,9 @@ class GlycanMapper {
* @returns {Promise} To check later if it has been processed.
* @memberof GlycanMapper
*/
private async parseGlycanMapping(mappingLink: string) {
return d3.json(mappingLink).then((i: Array<{}>) => {
i.forEach(glycan => glycan['het_codes'].forEach(x => this.mapping.set(x, glycan['name']))
i.forEach(glycan => glycan['het_codes'].forEach(x => this.glycanMapping.set(x, glycan['name']))
);
});
}
......
......@@ -16,7 +16,7 @@
"src/model.ts",
"src/visuals.ts",
"src/visualization.ts",
"src/glycans.ts",
"src/visualsMapping.ts",
"src/depictions.ts",
],
"exclude": [
......
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