Current Dev State

This commit is contained in:
Tim Lorsbach
2025-06-23 20:13:54 +02:00
parent b4f9bb277d
commit ded50edaa2
22617 changed files with 4345095 additions and 174 deletions

View File

@ -0,0 +1,295 @@
/****************************************************************************
* Copyright 2017 EPAM Systems
*
* 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.
***************************************************************************/
var util = require('./util');
var Vec2 = require('../util/vec2');
var Raphael = require('../raphael-ext');
var tfx = util.tfx;
function arrow(paper, a, b, options) {
var width = 5,
length = 7;
return paper.path('M{0},{1}L{2},{3}L{4},{5}M{2},{3}L{4},{6}', tfx(a.x), tfx(a.y), tfx(b.x), tfx(b.y), tfx(b.x - length), tfx(b.y - width), tfx(b.y + width))
.attr(options.lineattr);
}
function plus(paper, c, options) {
var s = options.scale / 5;
return paper.path('M{0},{4}L{0},{5}M{2},{1}L{3},{1}', tfx(c.x), tfx(c.y), tfx(c.x - s), tfx(c.x + s), tfx(c.y - s), tfx(c.y + s))
.attr(options.lineattr);
}
function bondSingle(paper, hb1, hb2, options) {
var a = hb1.p,
b = hb2.p;
return paper.path(makeStroke(a, b))
.attr(options.lineattr);
}
function bondSingleUp(paper, a, b2, b3, options) { // eslint-disable-line max-params
return paper.path('M{0},{1}L{2},{3}L{4},{5}Z', tfx(a.x), tfx(a.y), tfx(b2.x), tfx(b2.y), tfx(b3.x), tfx(b3.y))
.attr(options.lineattr).attr({ fill: '#000' });
}
function bondSingleStereoBold(paper, a1, a2, a3, a4, options) { // eslint-disable-line max-params
return paper.path('M{0},{1}L{2},{3}L{4},{5}L{6},{7}Z',
tfx(a1.x), tfx(a1.y), tfx(a2.x), tfx(a2.y), tfx(a3.x), tfx(a3.y), tfx(a4.x), tfx(a4.y))
.attr(options.lineattr).attr({
stroke: '#000',
fill: '#000'
});
}
function bondDoubleStereoBold(paper, sgBondPath, b1, b2, options) { // eslint-disable-line max-params
return paper.set([sgBondPath, paper.path('M{0},{1}L{2},{3}', tfx(b1.x), tfx(b1.y), tfx(b2.x), tfx(b2.y))
.attr(options.lineattr)]);
}
function bondSingleDown(paper, hb1, d, nlines, step, options) { // eslint-disable-line max-params
var a = hb1.p,
n = hb1.norm;
var bsp = 0.7 * options.stereoBond;
var path = '',
p,
q,
r;
for (var i = 0; i < nlines; ++i) {
r = a.addScaled(d, step * i);
p = r.addScaled(n, bsp * (i + 0.5) / (nlines - 0.5));
q = r.addScaled(n, -bsp * (i + 0.5) / (nlines - 0.5));
path += makeStroke(p, q);
}
return paper.path(path).attr(options.lineattr);
}
function bondSingleEither(paper, hb1, d, nlines, step, options) { // eslint-disable-line max-params
var a = hb1.p,
n = hb1.norm;
var bsp = 0.7 * options.stereoBond;
var path = 'M' + tfx(a.x) + ',' + tfx(a.y),
r = a;
for (var i = 0; i < nlines; ++i) {
r = a.addScaled(d, step * (i + 0.5)).addScaled(n,
((i & 1) ? -1 : +1) * bsp * (i + 0.5) / (nlines - 0.5));
path += 'L' + tfx(r.x) + ',' + tfx(r.y);
}
return paper.path(path)
.attr(options.lineattr);
}
function bondDouble(paper, a1, a2, b1, b2, cisTrans, options) { // eslint-disable-line max-params
return paper.path(cisTrans ?
'M{0},{1}L{6},{7}M{4},{5}L{2},{3}' :
'M{0},{1}L{2},{3}M{4},{5}L{6},{7}',
tfx(a1.x), tfx(a1.y), tfx(b1.x), tfx(b1.y), tfx(a2.x), tfx(a2.y), tfx(b2.x), tfx(b2.y))
.attr(options.lineattr);
}
function bondSingleOrDouble(paper, hb1, hb2, nSect, options) { // eslint-disable-line max-statements, max-params
var a = hb1.p,
b = hb2.p,
n = hb1.norm;
var bsp = options.bondSpace / 2;
var path = '',
pi,
pp = a;
for (var i = 1; i <= nSect; ++i) {
pi = Vec2.lc2(a, (nSect - i) / nSect, b, i / nSect);
if (i & 1) {
path += makeStroke(pp, pi);
} else {
path += makeStroke(pp.addScaled(n, bsp), pi.addScaled(n, bsp));
path += makeStroke(pp.addScaled(n, -bsp), pi.addScaled(n, -bsp));
}
pp = pi;
}
return paper.path(path)
.attr(options.lineattr);
}
function bondTriple(paper, hb1, hb2, options) {
var a = hb1.p,
b = hb2.p,
n = hb1.norm;
var a2 = a.addScaled(n, options.bondSpace);
var b2 = b.addScaled(n, options.bondSpace);
var a3 = a.addScaled(n, -options.bondSpace);
var b3 = b.addScaled(n, -options.bondSpace);
return paper.path(makeStroke(a, b) + makeStroke(a2, b2) + makeStroke(a3, b3))
.attr(options.lineattr);
}
function bondAromatic(paper, paths, bondShift, options) {
var l1 = paper.path(paths[0]).attr(options.lineattr);
var l2 = paper.path(paths[1]).attr(options.lineattr);
if (bondShift !== undefined && bondShift !== null)
(bondShift > 0 ? l1 : l2).attr({ 'stroke-dasharray': '- ' });
return paper.set([l1, l2]);
}
function bondAny(paper, hb1, hb2, options) {
var a = hb1.p,
b = hb2.p;
return paper.path(makeStroke(a, b))
.attr(options.lineattr).attr({ 'stroke-dasharray': '- ' });
}
function reactingCenter(paper, p, options) {
var pathdesc = '';
for (var i = 0; i < p.length / 2; ++i)
pathdesc += makeStroke(p[2 * i], p[(2 * i) + 1]);
return paper.path(pathdesc).attr(options.lineattr);
}
function topologyMark(paper, p, mark, options) {
var path = paper.text(p.x, p.y, mark)
.attr({
'font': options.font,
'font-size': options.fontszsub,
'fill': '#000'
});
var rbb = util.relBox(path.getBBox());
recenterText(path, rbb);
return path;
}
function radicalCap(paper, p, options) {
var s = options.lineWidth * 0.9;
var dw = s,
dh = 2 * s;
return paper.path('M{0},{1}L{2},{3}L{4},{5}',
tfx(p.x - dw), tfx(p.y + dh), tfx(p.x), tfx(p.y), tfx(p.x + dw), tfx(p.y + dh))
.attr({
'stroke': '#000',
'stroke-width': options.lineWidth * 0.7,
'stroke-linecap': 'square',
'stroke-linejoin': 'miter'
});
}
function radicalBullet(paper, p, options) {
return paper.circle(tfx(p.x), tfx(p.y), options.lineWidth)
.attr({
stroke: null,
fill: '#000'
});
}
function bracket(paper, d, n, c, bracketWidth, bracketHeight, options) { // eslint-disable-line max-params
bracketWidth = bracketWidth || 0.25;
bracketHeight = bracketHeight || 1.0;
var a0 = c.addScaled(n, -0.5 * bracketHeight);
var a1 = c.addScaled(n, 0.5 * bracketHeight);
var b0 = a0.addScaled(d, -bracketWidth);
var b1 = a1.addScaled(d, -bracketWidth);
return paper.path('M{0},{1}L{2},{3}L{4},{5}L{6},{7}',
tfx(b0.x), tfx(b0.y), tfx(a0.x), tfx(a0.y),
tfx(a1.x), tfx(a1.y), tfx(b1.x), tfx(b1.y))
.attr(options.sgroupBracketStyle);
}
function selectionRectangle(paper, p0, p1, options) {
return paper.rect(tfx(Math.min(p0.x, p1.x)),
tfx(Math.min(p0.y, p1.y)),
tfx(Math.abs(p1.x - p0.x)),
tfx(Math.abs(p1.y - p0.y))).attr(options.lassoStyle);
}
function selectionPolygon(paper, r, options) {
var v = r[r.length - 1];
var pstr = 'M' + tfx(v.x) + ',' + tfx(v.y);
for (var i = 0; i < r.length; ++i)
pstr += 'L' + tfx(r[i].x) + ',' + tfx(r[i].y);
return paper.path(pstr).attr(options.lassoStyle);
}
function selectionLine(paper, p0, p1, options) {
return paper.path(makeStroke(p0, p1)).attr(options.lassoStyle);
}
function makeStroke(a, b) {
return 'M' + tfx(a.x) + ',' + tfx(a.y) +
'L' + tfx(b.x) + ',' + tfx(b.y) + ' ';
}
function dashedPath(p0, p1, dash) {
var t0 = 0;
var t1 = Vec2.dist(p0, p1);
var d = Vec2.diff(p1, p0).normalized();
var black = true;
var path = '';
var i = 0;
while (t0 < t1) {
var len = dash[i % dash.length];
var t2 = t0 + Math.min(len, t1 - t0);
if (black)
path += 'M ' + p0.addScaled(d, t0).coordStr() + ' L ' + p0.addScaled(d, t2).coordStr();
t0 += len;
black = !black;
i++;
}
return path;
}
function aromaticBondPaths(a2, a3, b2, b3, mask, dash) { // eslint-disable-line max-params
var l1 = dash && (mask & 1) ? dashedPath(a2, b2, dash) : makeStroke(a2, b2);
var l2 = dash && (mask & 2) ? dashedPath(a3, b3, dash) : makeStroke(a3, b3);
return [l1, l2];
}
function recenterText(path, rbb) {
// TODO: find a better way
if (Raphael.vml) { // dirty hack
console.assert(null, "Souldn't go here!");
var gap = rbb.height * 0.16;
path.translateAbs(0, gap);
rbb.y += gap;
}
}
module.exports = {
recenterText: recenterText,
arrow: arrow,
plus: plus,
aromaticBondPaths: aromaticBondPaths,
bondSingle: bondSingle,
bondSingleUp: bondSingleUp,
bondSingleStereoBold: bondSingleStereoBold,
bondDoubleStereoBold: bondDoubleStereoBold,
bondSingleDown: bondSingleDown,
bondSingleEither: bondSingleEither,
bondDouble: bondDouble,
bondSingleOrDouble: bondSingleOrDouble,
bondTriple: bondTriple,
bondAromatic: bondAromatic,
bondAny: bondAny,
reactingCenter: reactingCenter,
topologyMark: topologyMark,
radicalCap: radicalCap,
radicalBullet: radicalBullet,
bracket: bracket,
selectionRectangle: selectionRectangle,
selectionPolygon: selectionPolygon,
selectionLine: selectionLine
};

View File

@ -0,0 +1,210 @@
/****************************************************************************
* Copyright 2017 EPAM Systems
*
* 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.
***************************************************************************/
var Raphael = require('../raphael-ext');
var Box2Abs = require('../util/box2abs');
var Vec2 = require('../util/vec2');
var scale = require('../util/scale');
var Struct = require('../chem/struct');
var ReStruct = require('./restruct');
var defaultOptions = require('./options');
var DEBUG = { debug: false, logcnt: 0, logmouse: false, hl: false };
DEBUG.logMethod = function () { };
// DEBUG.logMethod = function (method) {addionalAtoms("METHOD: " + method);
function Render(clientArea, opt) {
this.userOpts = opt;
this.clientArea = clientArea;
this.paper = new Raphael(clientArea, 0, 0);
this.sz = Vec2.ZERO;
this.ctab = new ReStruct(new Struct(), this);
this.options = defaultOptions(this.userOpts);
}
Render.prototype.view2obj = function (p, isRelative) {
var scroll = this.scrollPos();
if (!this.useOldZoom) {
p = p.scaled(1 / this.options.zoom);
scroll = scroll.scaled(1 / this.options.zoom);
}
p = isRelative ? p : p.add(scroll).sub(this.options.offset);
return scale.scaled2obj(p, this.options);
};
Render.prototype.obj2view = function (v, isRelative) {
var p = scale.obj2scaled(v, this.options);
p = isRelative ? p : p.add(this.options.offset).sub(this.scrollPos().scaled(1 / this.options.zoom));
if (!this.useOldZoom)
p = p.scaled(this.options.zoom);
return p;
};
Render.prototype.scrollPos = function () {
return new Vec2(this.clientArea.scrollLeft, this.clientArea.scrollTop);
};
function cumulativeOffset(el) {
var curtop = 0;
var curleft = 0;
if (el.parentNode) {
do {
curtop += el.offsetTop || 0;
curleft += el.offsetLeft || 0;
el = el.offsetParent;
} while (el);
}
return { left: curleft, top: curtop };
}
Render.prototype.page2obj = function (pagePos) {
var offset = cumulativeOffset(this.clientArea);
var pp = new Vec2(pagePos.pageX - offset.left, pagePos.pageY - offset.top);
return this.view2obj(pp);
};
Render.prototype.setPaperSize = function (sz) {
DEBUG.logMethod('setPaperSize');
this.sz = sz;
this.paper.setSize(sz.x * this.options.zoom, sz.y * this.options.zoom);
this.setViewBox(this.options.zoom);
};
Render.prototype.setOffset = function (newoffset) {
DEBUG.logMethod('setOffset');
var delta = new Vec2(newoffset.x - this.options.offset.x, newoffset.y - this.options.offset.y);
this.clientArea.scrollLeft += delta.x;
this.clientArea.scrollTop += delta.y;
this.options.offset = newoffset;
};
Render.prototype.setZoom = function (zoom) {
// when scaling the canvas down it may happen that the scaled canvas is smaller than the view window
// don't forget to call setScrollOffset after zooming (or use extendCanvas directly)
console.info('set zoom', zoom);
this.options.zoom = zoom;
this.paper.setSize(this.sz.x * zoom, this.sz.y * zoom);
this.setViewBox(zoom);
};
function calcExtend(sSz, x0, y0, x1, y1) { // eslint-disable-line max-params
var ex = (x0 < 0) ? -x0 : 0;
var ey = (y0 < 0) ? -y0 : 0;
if (sSz.x < x1)
ex += x1 - sSz.x;
if (sSz.y < y1)
ey += y1 - sSz.y;
return new Vec2(ex, ey);
}
Render.prototype.setScrollOffset = function (x, y) {
var clientArea = this.clientArea;
var cx = clientArea.clientWidth;
var cy = clientArea.clientHeight;
var e = calcExtend(this.sz.scaled(this.options.zoom), x, y,
cx + x, cy + y).scaled(1 / this.options.zoom);
if (e.x > 0 || e.y > 0) {
this.setPaperSize(this.sz.add(e));
var d = new Vec2((x < 0) ? -x : 0,
(y < 0) ? -y : 0).scaled(1 / this.options.zoom);
if (d.x > 0 || d.y > 0) {
this.ctab.translate(d);
this.setOffset(this.options.offset.add(d));
}
}
clientArea.scrollLeft = x;
clientArea.scrollTop = y;
// TODO: store drag position in scaled systems
// scrollLeft = clientArea.scrollLeft;
// scrollTop = clientArea.scrollTop;
this.update(false);
};
Render.prototype.setScale = function (z) {
if (this.options.offset)
this.options.offset = this.options.offset.scaled(1 / z).scaled(z);
this.userOpts.scale *= z;
this.options = null;
this.update(true);
};
Render.prototype.setViewBox = function (z) {
if (!this.useOldZoom)
this.paper.canvas.setAttribute('viewBox', '0 0 ' + this.sz.x + ' ' + this.sz.y);
else
this.setScale(z);
};
Render.prototype.setMolecule = function (ctab) {
debugger;
DEBUG.logMethod('setMolecule');
this.paper.clear();
this.ctab = new ReStruct(ctab, this);
this.options.offset = new Vec2();
this.update(false);
};
Render.prototype.update = function (force, viewSz) { // eslint-disable-line max-statements
viewSz = viewSz || new Vec2(this.clientArea.clientWidth || 100,
this.clientArea.clientHeight || 100);
var changes = this.ctab.update(force);
this.ctab.setSelection(); // [MK] redraw the selection bits where necessary
if (changes) {
var sf = this.options.scale;
var bb = this.ctab.getVBoxObj().transform(scale.obj2scaled, this.options).translate(this.options.offset || new Vec2());
if (!this.options.autoScale) {
var ext = Vec2.UNIT.scaled(sf);
var eb = bb.sz().length() > 0 ? bb.extend(ext, ext) : bb;
var vb = new Box2Abs(this.scrollPos(), viewSz.scaled(1 / this.options.zoom).sub(Vec2.UNIT.scaled(20)));
var cb = Box2Abs.union(vb, eb);
if (!this.oldCb)
this.oldCb = new Box2Abs();
var sz = cb.sz().floor();
var delta = this.oldCb.p0.sub(cb.p0).ceil();
this.oldBb = bb;
if (!this.sz || sz.x != this.sz.x || sz.y != this.sz.y)
this.setPaperSize(sz);
this.options.offset = this.options.offset || new Vec2();
if (delta.x != 0 || delta.y != 0) {
this.setOffset(this.options.offset.add(delta));
this.ctab.translate(delta);
}
} else {
var sz1 = bb.sz();
var marg = this.options.autoScaleMargin;
var mv = new Vec2(marg, marg);
var csz = viewSz;
if (csz.x < (2 * marg) + 1 || csz.y < (2 * marg) + 1)
throw new Error('View box too small for the given margin');
var rescale = Math.max(sz1.x / (csz.x - (2 * marg)), sz1.y / (csz.y - (2 * marg)));
if (this.options.maxBondLength / rescale > 1.0)
rescale = 1.0;
var sz2 = sz1.add(mv.scaled(2 * rescale));
/* eslint-disable no-mixed-operators*/
this.paper.setViewBox(bb.pos().x - marg * rescale - (csz.x * rescale - sz2.x) / 2, bb.pos().y - marg * rescale - (csz.y * rescale - sz2.y) / 2, csz.x * rescale, csz.y * rescale);
/* eslint-enable no-mixed-operators*/
}
}
};
module.exports = Render;

View File

@ -0,0 +1,96 @@
/****************************************************************************
* Copyright 2017 EPAM Systems
*
* 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.
***************************************************************************/
var utils = require('../editor/tool/utils');
var Vec2 = require('../util/vec2');
function defaultOptions(opt) {
const scaleFactor = opt.scale || 100;
if (opt.rotationStep)
utils.setFracAngle(opt.rotationStep);
const labelFontSize = Math.ceil(1.9 * (scaleFactor / 6));
const subFontSize = Math.ceil(0.7 * labelFontSize);
const defaultOptions = {
// flags for debugging
showAtomIds: false,
showBondIds: false,
showHalfBondIds: false,
showLoopIds: false,
// rendering customization flags
hideChiralFlag: false,
showValenceWarnings: true,
autoScale: false, // scale structure to fit into the given view box, used in view mode
autoScaleMargin: 0,
maxBondLength: 0, // 0 stands for "not specified"
atomColoring: true,
hideImplicitHydrogen: false,
hideTerminalLabels: false,
// atoms
carbonExplicitly: false,
showCharge: true,
showHydrogenLabels: 'on',
showValence: true,
// bonds
aromaticCircle: true,
scale: scaleFactor,
zoom: 1.0,
offset: new Vec2(),
lineWidth: scaleFactor / 20,
bondSpace: opt.doubleBondWidth || scaleFactor / 7,
stereoBond: opt.stereoBondWidth || scaleFactor / 7,
subFontSize: subFontSize,
font: '30px Arial',
fontsz: labelFontSize,
fontszsub: subFontSize,
fontRLabel: labelFontSize * 1.2,
fontRLogic: labelFontSize * 0.7,
/* styles */
lineattr: {
'stroke': '#000',
'stroke-width': opt.bondThickness || scaleFactor / 20,
'stroke-linecap': 'round',
'stroke-linejoin': 'round'
},
/* eslint-enable quote-props */
selectionStyle: {
fill: '#7f7',
stroke: 'none'
},
highlightStyle: {
'stroke': '#0c0',
'stroke-width': 0.6 * scaleFactor / 20
},
sgroupBracketStyle: {
'stroke': 'darkgray',
'stroke-width': 0.5 * scaleFactor / 20
},
lassoStyle: {
'stroke': 'gray',
'stroke-width': '1px'
},
atomSelectionPlateRadius: labelFontSize * 1.2
};
return Object.assign({}, defaultOptions, opt);
}
module.exports = defaultOptions;

View File

@ -0,0 +1,642 @@
/****************************************************************************
* Copyright 2017 EPAM Systems
*
* 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.
***************************************************************************/
// ReStruct is to store all the auxiliary information for
// Struct while rendering
var Box2Abs = require('../../util/box2abs');
var Map = require('../../util/map');
var Pool = require('../../util/pool');
var Set = require('../../util/set');
var Vec2 = require('../../util/vec2');
var scale = require('../../util/scale');
var util = require('../util');
var Struct = require('../../chem/struct');
var ReAtom = require('./reatom');
var ReBond = require('./rebond');
var ReRxnPlus = require('./rerxnplus');
var ReRxnArrow = require('./rerxnarrow');
var ReFrag = require('./refrag');
var ReRGroup = require('./rergroup');
var ReDataSGroupData = require('./redatasgroupdata');
var ReChiralFlag = require('./rechiralflag');
var ReSGroup = require('./resgroup');
var ReLoop = require('./reloop');
var LAYER_MAP = {
background: 0,
selectionPlate: 1,
highlighting: 2,
warnings: 3,
data: 4,
indices: 5
};
function ReStruct(molecule, render) { // eslint-disable-line max-statements
this.render = render;
this.atoms = new Map();
this.bonds = new Map();
this.reloops = new Map();
this.rxnPluses = new Map();
this.rxnArrows = new Map();
this.frags = new Map();
this.rgroups = new Map();
this.sgroups = new Map();
this.sgroupData = new Map();
this.chiralFlags = new Map();
this.molecule = molecule || new Struct();
this.initialized = false;
this.layers = [];
this.initLayers();
this.connectedComponents = new Pool();
this.ccFragmentType = new Map();
for (var map in ReStruct.maps)
this[map + 'Changed'] = {};
this.structChanged = false;
// TODO: eachItem ?
molecule.atoms.each(function (aid, atom) {
this.atoms.set(aid, new ReAtom(atom));
}, this);
molecule.bonds.each(function (bid, bond) {
this.bonds.set(bid, new ReBond(bond));
}, this);
molecule.loops.each(function (lid, loop) {
this.reloops.set(lid, new ReLoop(loop));
}, this);
molecule.rxnPluses.each(function (id, item) {
this.rxnPluses.set(id, new ReRxnPlus(item));
}, this);
molecule.rxnArrows.each(function (id, item) {
this.rxnArrows.set(id, new ReRxnArrow(item));
}, this);
molecule.frags.each(function (id, item) {
this.frags.set(id, new ReFrag(item));
}, this);
molecule.rgroups.each(function (id, item) {
this.rgroups.set(id, new ReRGroup(item));
}, this);
molecule.sgroups.each(function (id, item) {
this.sgroups.set(id, new ReSGroup(item));
if (item.type === 'DAT' && !item.data.attached)
this.sgroupData.set(id, new ReDataSGroupData(item)); // [MK] sort of a hack, we use the SGroup id for the data field id
}, this);
if (molecule.isChiral) {
var bb = molecule.getCoordBoundingBox();
this.chiralFlags.set(0, new ReChiralFlag(new Vec2(bb.max.x, bb.min.y - 1)));
}
}
ReStruct.prototype.connectedComponentRemoveAtom = function (aid, atom) {
atom = atom || this.atoms.get(aid);
if (atom.component < 0)
return;
var cc = this.connectedComponents.get(atom.component);
Set.remove(cc, aid);
if (Set.size(cc) < 1)
this.connectedComponents.remove(atom.component);
atom.component = -1;
};
ReStruct.prototype.clearConnectedComponents = function () {
this.connectedComponents.clear();
this.atoms.each(function (aid, atom) {
atom.component = -1;
});
};
ReStruct.prototype.getConnectedComponent = function (aid, adjacentComponents) {
var list = (typeof (aid['length']) === 'number') ? [].slice.call(aid) : [aid];
var ids = Set.empty();
while (list.length > 0) {
(function () {
var aid = list.pop();
Set.add(ids, aid);
var atom = this.atoms.get(aid);
if (atom.component >= 0)
Set.add(adjacentComponents, atom.component);
for (var i = 0; i < atom.a.neighbors.length; ++i) {
var neiId = this.molecule.halfBonds.get(atom.a.neighbors[i]).end;
if (!Set.contains(ids, neiId))
list.push(neiId);
}
}).apply(this);
}
return ids;
};
ReStruct.prototype.addConnectedComponent = function (ids) {
var compId = this.connectedComponents.add(ids);
var adjacentComponents = Set.empty();
var atomIds = this.getConnectedComponent(Set.list(ids), adjacentComponents);
Set.remove(adjacentComponents, compId);
var type = -1;
Set.each(atomIds, function (aid) {
var atom = this.atoms.get(aid);
atom.component = compId;
if (atom.a.rxnFragmentType != -1) {
if (type != -1 && atom.a.rxnFragmentType != type)
throw new Error('reaction fragment type mismatch');
type = atom.a.rxnFragmentType;
}
}, this);
this.ccFragmentType.set(compId, type);
return compId;
};
ReStruct.prototype.removeConnectedComponent = function (ccid) {
Set.each(this.connectedComponents.get(ccid), function (aid) {
this.atoms.get(aid).component = -1;
}, this);
return this.connectedComponents.remove(ccid);
};
// TODO: remove? not used
ReStruct.prototype.connectedComponentMergeIn = function (ccid, set) {
Set.each(set, function (aid) {
this.atoms.get(aid).component = ccid;
}, this);
Set.mergeIn(this.connectedComponents.get(ccid), set);
};
ReStruct.prototype.assignConnectedComponents = function () {
this.atoms.each(function (aid, atom) {
if (atom.component >= 0)
return;
var adjacentComponents = Set.empty();
var ids = this.getConnectedComponent(aid, adjacentComponents);
Set.each(adjacentComponents, function (ccid) {
this.removeConnectedComponent(ccid);
}, this);
this.addConnectedComponent(ids);
}, this);
};
// TODO: remove? not used
ReStruct.prototype.connectedComponentGetBoundingBox = function (ccid, cc, bb) {
cc = cc || this.connectedComponents.get(ccid);
bb = bb || { min: null, max: null };
Set.each(cc, function (aid) {
var ps = scale.obj2scaled(this.atoms.get(aid).a.pp, this.render.options);
if (bb.min == null) {
bb.min = bb.max = ps;
} else {
bb.min = bb.min.min(ps);
bb.max = bb.max.max(ps);
}
}, this);
return bb;
};
ReStruct.prototype.initLayers = function () {
for (var group in LAYER_MAP) {
this.layers[LAYER_MAP[group]] =
this.render.paper.rect(0, 0, 10, 10)
.attr({
class: group + 'Layer',
fill: '#000',
opacity: '0.0'
}).toFront();
}
};
ReStruct.prototype.addReObjectPath = function (group, visel, path, pos, visible) { // eslint-disable-line max-params
if (!path || !this.layers[LAYER_MAP[group]].node.parentNode)
return;
var offset = this.render.options.offset;
var bb = visible ? Box2Abs.fromRelBox(util.relBox(path.getBBox())) : null;
var ext = pos && bb ? bb.translate(pos.negated()) : null;
if (offset !== null) {
path.translateAbs(offset.x, offset.y);
bb = bb ? bb.translate(offset) : null;
}
visel.add(path, bb, ext);
path.insertBefore(this.layers[LAYER_MAP[group]]);
};
ReStruct.prototype.clearMarks = function () {
for (var map in ReStruct.maps)
this[map + 'Changed'] = {};
this.structChanged = false;
};
ReStruct.prototype.markItemRemoved = function () {
this.structChanged = true;
};
ReStruct.prototype.markBond = function (bid, mark) {
this.markItem('bonds', bid, mark);
};
ReStruct.prototype.markAtom = function (aid, mark) {
this.markItem('atoms', aid, mark);
};
ReStruct.prototype.markItem = function (map, id, mark) {
var mapChanged = this[map + 'Changed'];
mapChanged[id] = (typeof (mapChanged[id]) !== 'undefined') ?
Math.max(mark, mapChanged[id]) : mark;
if (this[map].has(id))
this.clearVisel(this[map].get(id).visel);
};
ReStruct.prototype.clearVisel = function (visel) {
for (var i = 0; i < visel.paths.length; ++i)
visel.paths[i].remove();
visel.clear();
};
ReStruct.prototype.eachItem = function (func, context) {
for (var map in ReStruct.maps) {
this[map].each(function (id, item) {
func.call(context, item);
});
}
};
ReStruct.prototype.getVBoxObj = function (selection) {
selection = selection || {};
if (isSelectionEmpty(selection)) {
for (var map in ReStruct.maps)
if (ReStruct.maps.hasOwnProperty(map)) selection[map] = this[map].keys();
}
var vbox = null;
for (map in ReStruct.maps) {
if (ReStruct.maps.hasOwnProperty(map) && selection[map]) {
selection[map].forEach(function (id) {
var box = this[map].get(id).getVBoxObj(this.render);
if (box)
vbox = vbox ? Box2Abs.union(vbox, box) : box.clone();
}, this);
}
}
vbox = vbox || new Box2Abs(0, 0, 0, 0);
return vbox;
};
function isSelectionEmpty(selection) {
if (selection) {
for (var map in ReStruct.maps) {
if (ReStruct.maps.hasOwnProperty(map) && selection[map] && selection[map].length > 0)
return false;
}
}
return true;
}
ReStruct.prototype.translate = function (d) {
this.eachItem(function (item) {
item.visel.translate(d);
});
};
ReStruct.prototype.scale = function (s) {
// NOTE: bounding boxes are not valid after scaling
this.eachItem(function (item) {
scaleVisel(item.visel, s);
});
};
function scaleRPath(path, s) {
if (path.type == 'set') { // TODO: rework scaling
for (var i = 0; i < path.length; ++i)
scaleRPath(path[i], s);
} else {
if (!(typeof path.attrs === 'undefined')) {
if ('font-size' in path.attrs)
path.attr('font-size', path.attrs['font-size'] * s);
else if ('stroke-width' in path.attrs)
path.attr('stroke-width', path.attrs['stroke-width'] * s);
}
path.scale(s, s, 0, 0);
}
}
function scaleVisel(visel, s) {
for (var i = 0; i < visel.paths.length; ++i)
scaleRPath(visel.paths[i], s);
}
ReStruct.prototype.clearVisels = function () {
this.eachItem(function (item) {
this.clearVisel(item.visel);
}, this);
};
ReStruct.prototype.update = function (force) { // eslint-disable-line max-statements
force = force || !this.initialized;
// check items to update
var id, map, mapChanged;
if (force) {
for (map in ReStruct.maps) {
if (ReStruct.maps.hasOwnProperty(map)) {
mapChanged = this[map + 'Changed'];
this[map].each(function (id) {
mapChanged[id] = 1;
}, this);
}
}
} else {
// check if some of the items marked are already gone
for (map in ReStruct.maps) {
if (ReStruct.maps.hasOwnProperty(map)) {
mapChanged = this[map + 'Changed'];
for (id in mapChanged) {
if (!this[map].has(id)) // eslint-disable-line max-depth
delete mapChanged[id];
}
}
}
}
for (id in this.atomsChanged)
this.connectedComponentRemoveAtom(id);
// clean up empty fragments
// TODO: fragment removal should be triggered by the action responsible for the fragment contents removal and form an operation of its own
var emptyFrags = this.frags.findAll(function (fid, frag) {
return !frag.calcBBox(this.render.ctab, fid, this.render);
}, this);
for (var j = 0; j < emptyFrags.length; ++j) {
var fid = emptyFrags[j];
this.clearVisel(this.frags.get(fid).visel);
this.frags.unset(fid);
this.molecule.frags.remove(fid);
}
for (map in ReStruct.maps) {
mapChanged = this[map + 'Changed'];
for (id in mapChanged) {
this.clearVisel(this[map].get(id).visel);
this.structChanged |= mapChanged[id] > 0;
}
}
// TODO: when to update sgroup?
this.sgroups.each(function (sid, sgroup) {
this.clearVisel(sgroup.visel);
sgroup.highlighting = null;
sgroup.selectionPlate = null;
}, this);
// TODO [RB] need to implement update-on-demand for fragments and r-groups
this.frags.each(function (frid, frag) {
this.clearVisel(frag.visel);
}, this);
this.rgroups.each(function (rgid, rgroup) {
this.clearVisel(rgroup.visel);
}, this);
if (force) { // clear and recreate all half-bonds
this.clearConnectedComponents();
this.molecule.initHalfBonds();
this.molecule.initNeighbors();
}
// only update half-bonds adjacent to atoms that have moved
this.molecule.updateHalfBonds(new Map(this.atomsChanged).findAll(function (aid, status) {
return status >= 0;
}, this));
this.molecule.sortNeighbors(new Map(this.atomsChanged).findAll(function (aid, status) {
return status >= 1;
}, this));
this.assignConnectedComponents();
this.setImplicitHydrogen();
this.initialized = true;
this.verifyLoops();
var updLoops = force || this.structChanged;
if (updLoops)
this.updateLoops();
this.showLabels();
this.showBonds();
if (updLoops)
this.showLoops();
this.showReactionSymbols();
this.showSGroups();
this.showFragments();
this.showRGroups();
if (this.render.options.hideChiralFlag !== true) {
this.chiralFlags.each(function (id, item) {
item.show(this, id, this.render.options);
}, this);
}
this.clearMarks();
return true;
};
ReStruct.prototype.updateLoops = function () {
this.reloops.each(function (rlid, reloop) {
this.clearVisel(reloop.visel);
}, this);
var ret = this.molecule.findLoops();
ret.bondsToMark.forEach(function (bid) {
this.markBond(bid, 1);
}, this);
ret.newLoops.forEach(function (loopId) {
this.reloops.set(loopId, new ReLoop(this.molecule.loops.get(loopId)));
}, this);
};
ReStruct.prototype.showLoops = function () {
var options = this.render.options;
this.reloops.each(function (rlid, reloop) {
reloop.show(this, rlid, options);
}, this);
};
ReStruct.prototype.showReactionSymbols = function () {
var options = this.render.options;
var item;
var id;
for (id in this.rxnArrowsChanged) {
item = this.rxnArrows.get(id);
item.show(this, id, options);
}
for (id in this.rxnPlusesChanged) {
item = this.rxnPluses.get(id);
item.show(this, id, options);
}
};
ReStruct.prototype.showSGroups = function () {
var options = this.render.options;
this.molecule.sGroupForest.getSGroupsBFS().reverse().forEach(function (id) {
var resgroup = this.sgroups.get(id);
resgroup.show(this, id, options);
}, this);
};
ReStruct.prototype.showFragments = function () {
this.frags.each(function (id, frag) {
var path = frag.draw(this.render, id);
if (path) this.addReObjectPath('data', frag.visel, path, null, true);
// TODO fragment selection & highlighting
}, this);
};
ReStruct.prototype.showRGroups = function () {
var options = this.render.options;
this.rgroups.each(function (id, rgroup) {
rgroup.show(this, id, options);
}, this);
};
ReStruct.prototype.eachCC = function (func, type, context) {
this.connectedComponents.each(function (ccid, cc) {
if (!type || this.ccFragmentType.get(ccid) == type)
func.call(context || this, ccid, cc);
}, this);
};
// TODO: remove? not used
ReStruct.prototype.getGroupBB = function (type) {
var bb = { min: null, max: null };
this.eachCC(function (ccid, cc) {
bb = this.connectedComponentGetBoundingBox(ccid, cc, bb);
}, type, this);
return bb;
};
ReStruct.prototype.setImplicitHydrogen = function () {
// calculate implicit hydrogens for atoms that have been modified
this.molecule.setImplicitHydrogen(Object.keys(this.atomsChanged));
};
ReStruct.prototype.loopRemove = function (loopId) {
if (!this.reloops.has(loopId))
return;
var reloop = this.reloops.get(loopId);
this.clearVisel(reloop.visel);
var bondlist = [];
for (var i = 0; i < reloop.loop.hbs.length; ++i) {
var hbid = reloop.loop.hbs[i];
if (this.molecule.halfBonds.has(hbid)) {
var hb = this.molecule.halfBonds.get(hbid);
hb.loop = -1;
this.markBond(hb.bid, 1);
this.markAtom(hb.begin, 1);
bondlist.push(hb.bid);
}
}
this.reloops.unset(loopId);
this.molecule.loops.remove(loopId);
};
ReStruct.prototype.verifyLoops = function () {
var toRemove = [];
this.reloops.each(function (rlid, reloop) {
if (!reloop.isValid(this.molecule, rlid))
toRemove.push(rlid);
}, this);
for (var i = 0; i < toRemove.length; ++i)
this.loopRemove(toRemove[i]);
};
ReStruct.prototype.showLabels = function () { // eslint-disable-line max-statements
var options = this.render.options;
for (var aid in this.atomsChanged) {
var atom = this.atoms.get(aid);
atom.show(this, aid, options);
}
};
ReStruct.prototype.showBonds = function () { // eslint-disable-line max-statements
var options = this.render.options;
for (var bid in this.bondsChanged) {
var bond = this.bonds.get(bid);
bond.show(this, bid, options);
}
};
ReStruct.prototype.setSelection = function (selection) {
var redraw = (arguments.length === 0); // render.update only
for (var map in ReStruct.maps) {
if (ReStruct.maps.hasOwnProperty(map) && ReStruct.maps[map].isSelectable()) {
this[map].each(function (id, item) {
var selected = redraw ? item.selected :
selection && selection[map] && selection[map].indexOf(id) > -1;
this.showItemSelection(item, selected);
}, this);
}
}
};
ReStruct.prototype.showItemSelection = function (item, selected) {
var exists = item.selectionPlate != null && !item.selectionPlate.removed;
// TODO: simplify me, who sets `removed`?
item.selected = selected;
if (item instanceof ReDataSGroupData) item.sgroup.selected = selected;
if (selected) {
if (!exists) {
var render = this.render;
var options = render.options;
var paper = render.paper;
item.selectionPlate = item.makeSelectionPlate(this, paper, options);
this.addReObjectPath('selectionPlate', item.visel, item.selectionPlate);
}
if (item.selectionPlate)
item.selectionPlate.show(); // TODO [RB] review
} else
if (exists && item.selectionPlate) {
item.selectionPlate.hide(); // TODO [RB] review
}
};
ReStruct.maps = {
atoms: ReAtom,
bonds: ReBond,
rxnPluses: ReRxnPlus,
rxnArrows: ReRxnArrow,
frags: ReFrag,
rgroups: ReRGroup,
sgroupData: ReDataSGroupData,
chiralFlags: ReChiralFlag,
sgroups: ReSGroup,
reloops: ReLoop
};
module.exports = Object.assign(ReStruct, {
Atom: ReAtom,
Bond: ReBond,
RxnPlus: ReRxnPlus,
RxnArrow: ReRxnArrow,
Frag: ReFrag,
RGroup: ReRGroup,
ChiralFlag: ReChiralFlag,
SGroup: ReSGroup
});

View File

@ -0,0 +1,647 @@
/****************************************************************************
* Copyright 2017 EPAM Systems
*
* 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.
***************************************************************************/
var Box2Abs = require('../../util/box2abs');
var ReObject = require('./reobject');
var scale = require('../../util/scale');
var element = require('../../chem/element');
var draw = require('../draw');
var util = require('../util');
var Vec2 = require('../../util/vec2');
var Struct = require('../../chem/struct');
function ReAtom(/* chem.Atom*/atom) {
this.init('atom');
this.a = atom; // TODO rename a to item
this.showLabel = false;
this.hydrogenOnTheLeft = false;
this.color = '#000000';
this.component = -1;
}
ReAtom.prototype = new ReObject();
ReAtom.isSelectable = function () {
return true;
};
ReAtom.prototype.getVBoxObj = function (render) {
if (this.visel.boundingBox)
return ReObject.prototype.getVBoxObj.call(this, render);
return new Box2Abs(this.a.pp, this.a.pp);
};
ReAtom.prototype.drawHighlight = function (render) {
var ret = this.makeHighlightPlate(render);
render.ctab.addReObjectPath('highlighting', this.visel, ret);
return ret;
};
ReAtom.prototype.makeHighlightPlate = function (render) {
var paper = render.paper;
var options = render.options;
var ps = scale.obj2scaled(this.a.pp, options);
return paper.circle(ps.x, ps.y, options.atomSelectionPlateRadius)
.attr(options.highlightStyle);
};
ReAtom.prototype.makeSelectionPlate = function (restruct, paper, styles) {
var ps = scale.obj2scaled(this.a.pp, restruct.render.options);
return paper.circle(ps.x, ps.y, styles.atomSelectionPlateRadius)
.attr(styles.selectionStyle);
};
ReAtom.prototype.show = function (restruct, aid, options) { // eslint-disable-line max-statements
var render = restruct.render;
var ps = scale.obj2scaled(this.a.pp, render.options);
this.hydrogenOnTheLeft = setHydrogenPos(restruct.molecule, this);
this.showLabel = labelIsVisible(restruct, render.options, this);
if (this.showLabel) {
var label = buildLabel(this, render.paper, ps, options);
var delta = 0.5 * options.lineWidth;
var rightMargin = label.rbb.width / 2;
var leftMargin = -label.rbb.width / 2;
var implh = Math.floor(this.a.implicitH);
var isHydrogen = label.text === 'H';
restruct.addReObjectPath('data', this.visel, label.path, ps, true);
var index = null;
if (options.showAtomIds) {
index = {};
index.text = aid.toString();
index.path = render.paper.text(ps.x, ps.y, index.text)
.attr({
'font': options.font,
'font-size': options.fontszsub,
'fill': '#070'
});
index.rbb = util.relBox(index.path.getBBox());
draw.recenterText(index.path, index.rbb);
restruct.addReObjectPath('indices', this.visel, index.path, ps);
}
this.setHighlight(this.highlight, render);
if (this.a.alias || this.a.pseudo) return;
var hydroIndex = null;
if (isHydrogen && implh > 0) {
hydroIndex = showHydroIndex(this, render, implh, rightMargin);
rightMargin += hydroIndex.rbb.width + delta;
restruct.addReObjectPath('data', this.visel, hydroIndex.path, ps, true);
}
if (this.a.radical != 0) {
var radical = showRadical(this, render);
restruct.addReObjectPath('data', this.visel, radical.path, ps, true);
}
if (this.a.isotope != 0) {
var isotope = showIsotope(this, render, leftMargin);
leftMargin -= isotope.rbb.width + delta;
restruct.addReObjectPath('data', this.visel, isotope.path, ps, true);
}
if (!isHydrogen && implh > 0 && displayHydrogen(options.showHydrogenLabels, this)) {
var data = showHydrogen(this, render, implh, {
hydrogen: {},
hydroIndex: hydroIndex,
rightMargin: rightMargin,
leftMargin: leftMargin
});
var hydrogen = data.hydrogen;
hydroIndex = data.hydroIndex;
rightMargin = data.rightMargin;
leftMargin = data.leftMargin;
restruct.addReObjectPath('data', this.visel, hydrogen.path, ps, true);
if (hydroIndex != null)
restruct.addReObjectPath('data', this.visel, hydroIndex.path, ps, true);
}
if (this.a.charge != 0 && options.showCharge) {
var charge = showCharge(this, render, rightMargin);
rightMargin += charge.rbb.width + delta;
restruct.addReObjectPath('data', this.visel, charge.path, ps, true);
}
if (this.a.explicitValence >= 0 && options.showValence) {
var valence = showExplicitValence(this, render, rightMargin);
rightMargin += valence.rbb.width + delta;
restruct.addReObjectPath('data', this.visel, valence.path, ps, true);
}
if (this.a.badConn && options.showValenceWarnings) {
var warning = showWarning(this, render, leftMargin, rightMargin);
restruct.addReObjectPath('warnings', this.visel, warning.path, ps, true);
}
if (index) {
/* eslint-disable no-mixed-operators*/
pathAndRBoxTranslate(index.path, index.rbb,
-0.5 * label.rbb.width - 0.5 * index.rbb.width - delta,
0.3 * label.rbb.height);
/* eslint-enable no-mixed-operators*/
}
}
if (this.a.attpnt) {
var lsb = bisectLargestSector(this, restruct.molecule);
showAttpnt(this, render, lsb, restruct.addReObjectPath.bind(restruct));
}
var aamText = getAamText(this);
var queryAttrsText = getQueryAttrsText(this);
// this includes both aam flags, if any, and query features, if any
// we render them together to avoid possible collisions
aamText = (queryAttrsText.length > 0 ? queryAttrsText + '\n' : '') + (aamText.length > 0 ? '.' + aamText + '.' : '');
if (aamText.length > 0) {
var elem = element.map[this.a.label];
var aamPath = render.paper.text(ps.x, ps.y, aamText).attr({
'font': options.font,
'font-size': options.fontszsub,
'fill': (options.atomColoring && elem && element[elem].color) ? element[elem].color : '#000'
});
var aamBox = util.relBox(aamPath.getBBox());
draw.recenterText(aamPath, aamBox);
var dir = bisectLargestSector(this, restruct.molecule);
var visel = this.visel;
var t = 3;
// estimate the shift to clear the atom label
for (var i = 0; i < visel.exts.length; ++i)
t = Math.max(t, Vec2.shiftRayBox(ps, dir, visel.exts[i].translate(ps)));
// estimate the shift backwards to account for the size of the aam/query text box itself
t += Vec2.shiftRayBox(ps, dir.negated(), Box2Abs.fromRelBox(aamBox));
dir = dir.scaled(8 + t);
pathAndRBoxTranslate(aamPath, aamBox, dir.x, dir.y);
restruct.addReObjectPath('data', this.visel, aamPath, ps, true);
}
};
function labelIsVisible(restruct, options, atom) {
var isVisibleTerminal = options.showHydrogenLabels !== 'off' &&
options.showHydrogenLabels !== 'Hetero';
if (atom.a.neighbors.length === 0 ||
(atom.a.neighbors.length < 2 && isVisibleTerminal) ||
(options.carbonExplicitly) ||
atom.a.label.toLowerCase() !== 'c' ||
(atom.a.badConn && options.showValenceWarnings) ||
atom.a.isotope != 0 ||
atom.a.radical != 0 ||
atom.a.charge != 0 ||
atom.a.explicitValence >= 0 ||
atom.a.atomList != null ||
atom.a.rglabel != null ||
atom.a.alias)
return true;
if (atom.a.neighbors.length == 2) {
var n1 = atom.a.neighbors[0];
var n2 = atom.a.neighbors[1];
var hb1 = restruct.molecule.halfBonds.get(n1);
var hb2 = restruct.molecule.halfBonds.get(n2);
var b1 = restruct.bonds.get(hb1.bid);
var b2 = restruct.bonds.get(hb2.bid);
if (b1.b.type == b2.b.type &&
b1.b.stereo == Struct.Bond.PATTERN.STEREO.NONE &&
b2.b.stereo == Struct.Bond.PATTERN.STEREO.NONE) {
if (Math.abs(Vec2.cross(hb1.dir, hb2.dir)) < 0.2)
return true;
}
}
return false;
}
function displayHydrogen(hydrogenLabels, atom) {
return ((hydrogenLabels === 'on') ||
(hydrogenLabels === 'Terminal' && atom.a.neighbors.length < 2) ||
(hydrogenLabels === 'Hetero' && atom.label.text.toLowerCase() !== 'c') ||
(hydrogenLabels === 'Terminal and Hetero' && (atom.a.neighbors.length < 2 || atom.label.text.toLowerCase() !== 'c')));
}
function setHydrogenPos(struct, atom) {
// check where should the hydrogen be put on the left of the label
if (atom.a.neighbors.length === 0) {
var elem = element.map[atom.a.label];
return !elem || !!element[elem].leftH;
}
var yl = 1,
yr = 1,
nl = 0,
nr = 0;
for (var i = 0; i < atom.a.neighbors.length; ++i) {
var d = struct.halfBonds.get(atom.a.neighbors[i]).dir;
if (d.x <= 0) {
yl = Math.min(yl, Math.abs(d.y));
nl++;
} else {
yr = Math.min(yr, Math.abs(d.y));
nr++;
}
}
return (yl < 0.51 || yr < 0.51) ? yr < yl : nr > nl;
}
function buildLabel(atom, paper, ps, options) { // eslint-disable-line max-statements
var label = {};
atom.color = 'black';
if (atom.a.atomList != null) {
label.text = atom.a.atomList.label();
} else if (atom.a.pseudo) {
label.text = atom.a.pseudo;
} else if (atom.a.alias) {
label.text = atom.a.alias;
} else if (atom.a.label === 'R#' && atom.a.rglabel != null) {
label.text = '';
for (var rgi = 0; rgi < 32; rgi++) {
if (atom.a.rglabel & (1 << rgi)) // eslint-disable-line max-depth
label.text += ('R' + (rgi + 1).toString());
}
if (label.text == '') label = 'R#'; // for structures that missed 'M RGP' tag in molfile
} else {
label.text = atom.a.label;
var elem = element.map[label.text];
if (options.atomColoring && elem)
atom.color = element[elem].color || '#000';
}
label.path = paper.text(ps.x, ps.y, label.text)
.attr({
'font': options.font,
'font-size': options.fontsz,
'fill': atom.color,
'font-style': atom.a.pseudo ? 'italic' : ''
});
label.rbb = util.relBox(label.path.getBBox());
draw.recenterText(label.path, label.rbb);
if (atom.a.atomList != null)
pathAndRBoxTranslate(label.path, label.rbb, (atom.hydrogenOnTheLeft ? -1 : 1) * (label.rbb.width - label.rbb.height) / 2, 0);
atom.label = label;
return label;
}
function showHydroIndex(atom, render, implh, rightMargin) {
var ps = scale.obj2scaled(atom.a.pp, render.options);
var options = render.options;
var delta = 0.5 * options.lineWidth;
var hydroIndex = {};
hydroIndex.text = (implh + 1).toString();
hydroIndex.path =
render.paper.text(ps.x, ps.y, hydroIndex.text)
.attr({
'font': options.font,
'font-size': options.fontszsub,
'fill': atom.color
});
hydroIndex.rbb = util.relBox(hydroIndex.path.getBBox());
draw.recenterText(hydroIndex.path, hydroIndex.rbb);
/* eslint-disable no-mixed-operators*/
pathAndRBoxTranslate(hydroIndex.path, hydroIndex.rbb,
rightMargin + 0.5 * hydroIndex.rbb.width + delta,
0.2 * atom.label.rbb.height);
/* eslint-enable no-mixed-operators*/
return hydroIndex;
}
function showRadical(atom, render) {
var ps = scale.obj2scaled(atom.a.pp, render.options);
var options = render.options;
var paper = render.paper;
var radical = {};
var hshift;
switch (atom.a.radical) {
case 1:
radical.path = paper.set();
hshift = 1.6 * options.lineWidth;
radical.path.push(
draw.radicalBullet(paper, ps.add(new Vec2(-hshift, 0)), options),
draw.radicalBullet(paper, ps.add(new Vec2(hshift, 0)), options));
radical.path.attr('fill', atom.color);
break;
case 2:
radical.path = paper.set();
radical.path.push(
draw.radicalBullet(paper, ps, options));
radical.path.attr('fill', atom.color);
break;
case 3:
radical.path = paper.set();
hshift = 1.6 * options.lineWidth;
radical.path.push(
draw.radicalCap(paper, ps.add(new Vec2(-hshift, 0)), options),
draw.radicalCap(paper, ps.add(new Vec2(hshift, 0)), options));
radical.path.attr('stroke', atom.color);
break;
default:
break;
}
radical.rbb = util.relBox(radical.path.getBBox());
var vshift = -0.5 * (atom.label.rbb.height + radical.rbb.height);
if (atom.a.radical === 3)
vshift -= options.lineWidth / 2;
pathAndRBoxTranslate(radical.path, radical.rbb,
0, vshift);
return radical;
}
function showIsotope(atom, render, leftMargin) {
var ps = scale.obj2scaled(atom.a.pp, render.options);
var options = render.options;
var delta = 0.5 * options.lineWidth;
var isotope = {};
isotope.text = atom.a.isotope.toString();
isotope.path = render.paper.text(ps.x, ps.y, isotope.text)
.attr({
'font': options.font,
'font-size': options.fontszsub,
'fill': atom.color
});
isotope.rbb = util.relBox(isotope.path.getBBox());
draw.recenterText(isotope.path, isotope.rbb);
/* eslint-disable no-mixed-operators*/
pathAndRBoxTranslate(isotope.path, isotope.rbb,
leftMargin - 0.5 * isotope.rbb.width - delta,
-0.3 * atom.label.rbb.height);
/* eslint-enable no-mixed-operators*/
return isotope;
}
function showCharge(atom, render, rightMargin) {
var ps = scale.obj2scaled(atom.a.pp, render.options);
var options = render.options;
var delta = 0.5 * options.lineWidth;
var charge = {};
charge.text = '';
var absCharge = Math.abs(atom.a.charge);
if (absCharge != 1)
charge.text = absCharge.toString();
if (atom.a.charge < 0)
charge.text += '\u2013';
else
charge.text += '+';
charge.path = render.paper.text(ps.x, ps.y, charge.text)
.attr({
'font': options.font,
'font-size': options.fontszsub,
'fill': atom.color
});
charge.rbb = util.relBox(charge.path.getBBox());
draw.recenterText(charge.path, charge.rbb);
/* eslint-disable no-mixed-operators*/
pathAndRBoxTranslate(charge.path, charge.rbb,
rightMargin + 0.5 * charge.rbb.width + delta,
-0.3 * atom.label.rbb.height);
/* eslint-enable no-mixed-operators*/
return charge;
}
function showExplicitValence(atom, render, rightMargin) {
var mapValence = {
0: '0',
1: 'I',
2: 'II',
3: 'III',
4: 'IV',
5: 'V',
6: 'VI',
7: 'VII',
8: 'VIII',
9: 'IX',
10: 'X',
11: 'XI',
12: 'XII',
13: 'XIII',
14: 'XIV'
};
var ps = scale.obj2scaled(atom.a.pp, render.options);
var options = render.options;
var delta = 0.5 * options.lineWidth;
var valence = {};
valence.text = mapValence[atom.a.explicitValence];
if (!valence.text)
throw new Error('invalid valence ' + atom.a.explicitValence.toString());
valence.text = '(' + valence.text + ')';
valence.path = render.paper.text(ps.x, ps.y, valence.text)
.attr({
'font': options.font,
'font-size': options.fontszsub,
'fill': atom.color
});
valence.rbb = util.relBox(valence.path.getBBox());
draw.recenterText(valence.path, valence.rbb);
/* eslint-disable no-mixed-operators*/
pathAndRBoxTranslate(valence.path, valence.rbb,
rightMargin + 0.5 * valence.rbb.width + delta,
-0.3 * atom.label.rbb.height);
/* eslint-enable no-mixed-operators*/
return valence;
}
function showHydrogen(atom, render, implh, data) { // eslint-disable-line max-statements
var hydroIndex = data.hydroIndex;
var hydrogenLeft = atom.hydrogenOnTheLeft;
var ps = scale.obj2scaled(atom.a.pp, render.options);
var options = render.options;
var delta = 0.5 * options.lineWidth;
var hydrogen = data.hydrogen;
hydrogen.text = 'H';
hydrogen.path = render.paper.text(ps.x, ps.y, hydrogen.text).attr({
'font': options.font,
'font-size': options.fontsz,
'fill': atom.color
});
hydrogen.rbb = util.relBox(hydrogen.path.getBBox());
draw.recenterText(hydrogen.path, hydrogen.rbb);
if (!hydrogenLeft) {
pathAndRBoxTranslate(hydrogen.path, hydrogen.rbb,
data.rightMargin + (0.5 * hydrogen.rbb.width) + delta, 0);
data.rightMargin += hydrogen.rbb.width + delta;
}
if (implh > 1) {
hydroIndex = {};
hydroIndex.text = implh.toString();
hydroIndex.path = render.paper.text(ps.x, ps.y, hydroIndex.text)
.attr({
'font': options.font,
'font-size': options.fontszsub,
'fill': atom.color
});
hydroIndex.rbb = util.relBox(hydroIndex.path.getBBox());
draw.recenterText(hydroIndex.path, hydroIndex.rbb);
if (!hydrogenLeft) {
pathAndRBoxTranslate(hydroIndex.path, hydroIndex.rbb,
data.rightMargin + (0.5 * hydroIndex.rbb.width) + delta,
0.2 * atom.label.rbb.height);
data.rightMargin += hydroIndex.rbb.width + delta;
}
}
if (hydrogenLeft) {
if (hydroIndex != null) {
pathAndRBoxTranslate(hydroIndex.path, hydroIndex.rbb,
data.leftMargin - (0.5 * hydroIndex.rbb.width) - delta,
0.2 * atom.label.rbb.height);
data.leftMargin -= hydroIndex.rbb.width + delta;
}
pathAndRBoxTranslate(hydrogen.path, hydrogen.rbb,
data.leftMargin - (0.5 * hydrogen.rbb.width) - delta, 0);
data.leftMargin -= hydrogen.rbb.width + delta;
}
return Object.assign(data, { hydrogen: hydrogen, hydroIndex: hydroIndex });
}
function showWarning(atom, render, leftMargin, rightMargin) {
var ps = scale.obj2scaled(atom.a.pp, render.options);
var delta = 0.5 * render.options.lineWidth;
var tfx = util.tfx;
var warning = {};
var y = ps.y + (atom.label.rbb.height / 2) + delta;
warning.path = render.paper.path('M{0},{1}L{2},{3}',
tfx(ps.x + leftMargin), tfx(y), tfx(ps.x + rightMargin), tfx(y))
.attr(render.options.lineattr).attr({ stroke: '#F00' });
warning.rbb = util.relBox(warning.path.getBBox());
return warning;
}
function showAttpnt(atom, render, lsb, addReObjectPath) { // eslint-disable-line max-statements
var asterisk = '';
var ps = scale.obj2scaled(atom.a.pp, render.options);
var options = render.options;
var tfx = util.tfx;
var i, c, j; // eslint-disable-line no-unused-vars
for (i = 0, c = 0; i < 4; ++i) {
var attpntText = '';
if (atom.a.attpnt & (1 << i)) {
if (attpntText.length > 0)
attpntText += ' ';
attpntText += asterisk;
for (j = 0; j < (i == 0 ? 0 : (i + 1)); ++j)
attpntText += '\'';
var pos0 = new Vec2(ps);
var pos1 = ps.addScaled(lsb, 0.7 * options.scale);
var attpntPath1 = render.paper.text(pos1.x, pos1.y, attpntText)
.attr({
'font': options.font,
'font-size': options.fontsz,
'fill': atom.color
});
var attpntRbb = util.relBox(attpntPath1.getBBox());
draw.recenterText(attpntPath1, attpntRbb);
var lsbn = lsb.negated();
/* eslint-disable no-mixed-operators*/
pos1 = pos1.addScaled(lsbn, Vec2.shiftRayBox(pos1, lsbn, Box2Abs.fromRelBox(attpntRbb)) + options.lineWidth / 2);
/* eslint-enable no-mixed-operators*/
pos0 = shiftBondEnd(atom, pos0, lsb, options.lineWidth);
var n = lsb.rotateSC(1, 0);
var arrowLeft = pos1.addScaled(n, 0.05 * options.scale).addScaled(lsbn, 0.09 * options.scale);
var arrowRight = pos1.addScaled(n, -0.05 * options.scale).addScaled(lsbn, 0.09 * options.scale);
var attpntPath = render.paper.set();
attpntPath.push(
attpntPath1,
render.paper.path('M{0},{1}L{2},{3}M{4},{5}L{2},{3}L{6},{7}', tfx(pos0.x), tfx(pos0.y), tfx(pos1.x), tfx(pos1.y), tfx(arrowLeft.x), tfx(arrowLeft.y), tfx(arrowRight.x), tfx(arrowRight.y))
.attr(render.options.lineattr).attr({ 'stroke-width': options.lineWidth / 2 })
);
addReObjectPath('indices', atom.visel, attpntPath, ps);
lsb = lsb.rotate(Math.PI / 6);
}
}
}
function getAamText(atom) {
var aamText = '';
if (atom.a.aam > 0) aamText += atom.a.aam;
if (atom.a.invRet > 0) {
if (aamText.length > 0) aamText += ',';
if (atom.a.invRet == 1) aamText += 'Inv';
else if (atom.a.invRet == 2) aamText += 'Ret';
else throw new Error('Invalid value for the invert/retain flag');
}
if (atom.a.exactChangeFlag > 0) {
if (aamText.length > 0) aamText += ',';
if (atom.a.exactChangeFlag == 1) aamText += 'ext';
else throw new Error('Invalid value for the exact change flag');
}
return aamText;
}
function getQueryAttrsText(atom) {
var queryAttrsText = '';
if (atom.a.ringBondCount != 0) {
if (atom.a.ringBondCount > 0) queryAttrsText += 'rb' + atom.a.ringBondCount.toString();
else if (atom.a.ringBondCount == -1) queryAttrsText += 'rb0';
else if (atom.a.ringBondCount == -2) queryAttrsText += 'rb*';
else throw new Error('Ring bond count invalid');
}
if (atom.a.substitutionCount != 0) {
if (queryAttrsText.length > 0) queryAttrsText += ',';
if (atom.a.substitutionCount > 0) queryAttrsText += 's' + atom.a.substitutionCount.toString();
else if (atom.a.substitutionCount == -1) queryAttrsText += 's0';
else if (atom.a.substitutionCount == -2) queryAttrsText += 's*';
else throw new Error('Substitution count invalid');
}
if (atom.a.unsaturatedAtom > 0) {
if (queryAttrsText.length > 0) queryAttrsText += ',';
if (atom.a.unsaturatedAtom == 1) queryAttrsText += 'u';
else throw new Error('Unsaturated atom invalid value');
}
if (atom.a.hCount > 0) {
if (queryAttrsText.length > 0) queryAttrsText += ',';
queryAttrsText += 'H' + (atom.a.hCount - 1).toString();
}
return queryAttrsText;
}
function pathAndRBoxTranslate(path, rbb, x, y) {
path.translateAbs(x, y);
rbb.x += x;
rbb.y += y;
}
function bisectLargestSector(atom, struct) {
var angles = [];
atom.a.neighbors.forEach(function (hbid) {
var hb = struct.halfBonds.get(hbid);
angles.push(hb.ang);
});
angles = angles.sort(function (a, b) {
return a - b;
});
var da = [];
for (var i = 0; i < angles.length - 1; ++i)
da.push(angles[(i + 1) % angles.length] - angles[i]);
da.push(angles[0] - angles[angles.length - 1] + (2 * Math.PI));
var daMax = 0;
var ang = -Math.PI / 2;
for (i = 0; i < angles.length; ++i) {
if (da[i] > daMax) {
daMax = da[i];
ang = angles[i] + (da[i] / 2);
}
}
return new Vec2(Math.cos(ang), Math.sin(ang));
}
function shiftBondEnd(atom, pos0, dir, margin) {
var t = 0;
var visel = atom.visel;
for (var k = 0; k < visel.exts.length; ++k) {
var box = visel.exts[k].translate(pos0);
t = Math.max(t, Vec2.shiftRayBox(pos0, dir, box));
}
if (t > 0)
pos0 = pos0.addScaled(dir, t + margin);
return pos0;
}
module.exports = ReAtom;

View File

@ -0,0 +1,587 @@
/****************************************************************************
* Copyright 2017 EPAM Systems
*
* 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.
***************************************************************************/
var ReObject = require('./reobject');
var Struct = require('../../chem/struct');
var draw = require('../draw');
var Vec2 = require('../../util/vec2');
var util = require('../util');
var scale = require('../../util/scale');
function ReBond(/* chem.Bond*/bond) {
this.init('bond');
this.b = bond; // TODO rename b to item
this.doubleBondShift = 0;
}
ReBond.prototype = new ReObject();
ReBond.isSelectable = function () {
return true;
};
ReBond.prototype.drawHighlight = function (render) {
var ret = this.makeHighlightPlate(render);
render.ctab.addReObjectPath('highlighting', this.visel, ret);
return ret;
};
ReBond.prototype.makeHighlightPlate = function (render) {
var options = render.options;
bondRecalc(this, render.ctab, options);
var c = scale.obj2scaled(this.b.center, options);
return render.paper.circle(c.x, c.y, 0.8 * options.atomSelectionPlateRadius)
.attr(options.highlightStyle);
};
ReBond.prototype.makeSelectionPlate = function (restruct, paper, options) {
bondRecalc(this, restruct, options);
var c = scale.obj2scaled(this.b.center, options);
return paper.circle(c.x, c.y, 0.8 * options.atomSelectionPlateRadius)
.attr(options.selectionStyle);
};
ReBond.prototype.show = function (restruct, bid, options) { // eslint-disable-line max-statements
var render = restruct.render;
var struct = restruct.molecule;
var paper = render.paper;
var hb1 = struct.halfBonds.get(this.b.hb1),
hb2 = struct.halfBonds.get(this.b.hb2);
checkStereoBold(bid, this, restruct);
bondRecalc(this, restruct, options);
setDoubleBondShift(this, struct);
this.path = getBondPath(restruct, this, hb1, hb2);
this.rbb = util.relBox(this.path.getBBox());
restruct.addReObjectPath('data', this.visel, this.path, null, true);
var reactingCenter = {};
reactingCenter.path = getReactingCenterPath(render, this, hb1, hb2);
if (reactingCenter.path) {
reactingCenter.rbb = util.relBox(reactingCenter.path.getBBox());
restruct.addReObjectPath('data', this.visel, reactingCenter.path, null, true);
}
var topology = {};
topology.path = getTopologyMark(render, this, hb1, hb2);
if (topology.path) {
topology.rbb = util.relBox(topology.path.getBBox());
restruct.addReObjectPath('data', this.visel, topology.path, null, true);
}
this.setHighlight(this.highlight, render);
var ipath = null;
var bondIdxOff = options.subFontSize * 0.6;
if (options.showBondIds) {
ipath = getIdsPath(bid, paper, hb1, hb2, bondIdxOff, 0.5, 0.5, hb1.norm);
restruct.addReObjectPath('indices', this.visel, ipath);
}
if (options.showHalfBondIds) {
ipath = getIdsPath(this.b.hb1, paper, hb1, hb2, bondIdxOff, 0.8, 0.2, hb1.norm);
restruct.addReObjectPath('indices', this.visel, ipath);
ipath = getIdsPath(this.b.hb2, paper, hb1, hb2, bondIdxOff, 0.2, 0.8, hb2.norm);
restruct.addReObjectPath('indices', this.visel, ipath);
}
if (options.showLoopIds && !options.showBondIds) {
ipath = getIdsPath(hb1.loop, paper, hb1, hb2, bondIdxOff, 0.5, 0.5, hb2.norm);
restruct.addReObjectPath('indices', this.visel, ipath);
ipath = getIdsPath(hb2.loop, paper, hb1, hb2, bondIdxOff, 0.5, 0.5, hb1.norm);
restruct.addReObjectPath('indices', this.visel, ipath);
}
};
function findIncomingStereoUpBond(atom, bid0, includeBoldStereoBond, restruct) {
return atom.neighbors.findIndex(function (hbid) {
var hb = restruct.molecule.halfBonds.get(hbid);
var bid = hb.bid;
if (bid === bid0)
return false;
var neibond = restruct.bonds.get(bid);
if (neibond.b.type === Struct.Bond.PATTERN.TYPE.SINGLE && neibond.b.stereo === Struct.Bond.PATTERN.STEREO.UP)
return neibond.b.end === hb.begin || (neibond.boldStereo && includeBoldStereoBond);
return !!(neibond.b.type === Struct.Bond.PATTERN.TYPE.DOUBLE && neibond.b.stereo === Struct.Bond.PATTERN.STEREO.NONE && includeBoldStereoBond && neibond.boldStereo);
});
}
function findIncomingUpBonds(bid0, bond, restruct) {
var halfbonds = [bond.b.begin, bond.b.end].map(function (aid) {
var atom = restruct.molecule.atoms.get(aid);
var pos = findIncomingStereoUpBond(atom, bid0, true, restruct);
return pos < 0 ? -1 : atom.neighbors[pos];
}, this);
console.assert(halfbonds.length === 2);
bond.neihbid1 = restruct.atoms.get(bond.b.begin).showLabel ? -1 : halfbonds[0];
bond.neihbid2 = restruct.atoms.get(bond.b.end).showLabel ? -1 : halfbonds[1];
}
function checkStereoBold(bid0, bond, restruct) {
var halfbonds = [bond.b.begin, bond.b.end].map(function (aid) {
var atom = restruct.molecule.atoms.get(aid);
var pos = findIncomingStereoUpBond(atom, bid0, false, restruct);
return pos < 0 ? -1 : atom.neighbors[pos];
}, restruct);
console.assert(halfbonds.length === 2);
bond.boldStereo = halfbonds[0] >= 0 && halfbonds[1] >= 0;
}
function getBondPath(restruct, bond, hb1, hb2) {
var path = null;
var render = restruct.render;
var struct = restruct.molecule;
var shiftA = !restruct.atoms.get(hb1.begin).showLabel;
var shiftB = !restruct.atoms.get(hb2.begin).showLabel;
switch (bond.b.type) {
case Struct.Bond.PATTERN.TYPE.SINGLE:
switch (bond.b.stereo) {
case Struct.Bond.PATTERN.STEREO.UP:
findIncomingUpBonds(hb1.bid, bond, restruct);
if (bond.boldStereo && bond.neihbid1 >= 0 && bond.neihbid2 >= 0)
path = getBondSingleStereoBoldPath(render, hb1, hb2, bond, struct);
else
path = getBondSingleUpPath(render, hb1, hb2, bond, struct);
break;
case Struct.Bond.PATTERN.STEREO.DOWN:
path = getBondSingleDownPath(render, hb1, hb2);
break;
case Struct.Bond.PATTERN.STEREO.EITHER:
path = getBondSingleEitherPath(render, hb1, hb2);
break;
default:
path = draw.bondSingle(render.paper, hb1, hb2, render.options);
break;
}
break;
case Struct.Bond.PATTERN.TYPE.DOUBLE:
findIncomingUpBonds(hb1.bid, bond, restruct);
if (bond.b.stereo === Struct.Bond.PATTERN.STEREO.NONE && bond.boldStereo &&
bond.neihbid1 >= 0 && bond.neihbid2 >= 0)
path = getBondDoubleStereoBoldPath(render, hb1, hb2, bond, struct, shiftA, shiftB);
else
path = getBondDoublePath(render, hb1, hb2, bond, shiftA, shiftB);
break;
case Struct.Bond.PATTERN.TYPE.TRIPLE:
path = draw.bondTriple(render.paper, hb1, hb2, render.options);
break;
case Struct.Bond.PATTERN.TYPE.AROMATIC:
var inAromaticLoop = (hb1.loop >= 0 && struct.loops.get(hb1.loop).aromatic) ||
(hb2.loop >= 0 && struct.loops.get(hb2.loop).aromatic);
path = inAromaticLoop ? draw.bondSingle(render.paper, hb1, hb2, render.options) :
getBondAromaticPath(render, hb1, hb2, bond, shiftA, shiftB);
break;
case Struct.Bond.PATTERN.TYPE.SINGLE_OR_DOUBLE:
path = getSingleOrDoublePath(render, hb1, hb2);
break;
case Struct.Bond.PATTERN.TYPE.SINGLE_OR_AROMATIC:
path = getBondAromaticPath(render, hb1, hb2, bond, shiftA, shiftB);
break;
case Struct.Bond.PATTERN.TYPE.DOUBLE_OR_AROMATIC:
path = getBondAromaticPath(render, hb1, hb2, bond, shiftA, shiftB);
break;
case Struct.Bond.PATTERN.TYPE.ANY:
path = draw.bondAny(render.paper, hb1, hb2, render.options);
break;
default:
throw new Error('Bond type ' + bond.b.type + ' not supported');
}
return path;
}
/* Get Path */
function getBondSingleUpPath(render, hb1, hb2, bond, struct) { // eslint-disable-line max-params
var a = hb1.p,
b = hb2.p,
n = hb1.norm;
var options = render.options;
var bsp = 0.7 * options.stereoBond;
var b2 = b.addScaled(n, bsp);
var b3 = b.addScaled(n, -bsp);
if (bond.neihbid2 >= 0) { // if the end is shared with another up-bond heading this way
var coords = stereoUpBondGetCoordinates(hb2, bond.neihbid2, options.stereoBond, struct);
b2 = coords[0];
b3 = coords[1];
}
return draw.bondSingleUp(render.paper, a, b2, b3, options);
}
function getBondSingleStereoBoldPath(render, hb1, hb2, bond, struct) { // eslint-disable-line max-params
var options = render.options;
var coords1 = stereoUpBondGetCoordinates(hb1, bond.neihbid1, options.stereoBond, struct);
var coords2 = stereoUpBondGetCoordinates(hb2, bond.neihbid2, options.stereoBond, struct);
var a1 = coords1[0];
var a2 = coords1[1];
var a3 = coords2[0];
var a4 = coords2[1];
return draw.bondSingleStereoBold(render.paper, a1, a2, a3, a4, options);
}
function getBondDoubleStereoBoldPath(render, hb1, hb2, bond, struct, shiftA, shiftB) { // eslint-disable-line max-params
var a = hb1.p,
b = hb2.p,
n = hb1.norm,
shift = bond.doubleBondShift;
var bsp = 1.5 * render.options.stereoBond;
var b1 = a.addScaled(n, bsp * shift);
var b2 = b.addScaled(n, bsp * shift);
if (shift > 0) {
if (shiftA)
b1 = b1.addScaled(hb1.dir, bsp * getBondLineShift(hb1.rightCos, hb1.rightSin));
if (shiftB)
b2 = b2.addScaled(hb1.dir, -bsp * getBondLineShift(hb2.leftCos, hb2.leftSin));
} else if (shift < 0) {
if (shiftA)
b1 = b1.addScaled(hb1.dir, bsp * getBondLineShift(hb1.leftCos, hb1.leftSin));
if (shiftB)
b2 = b2.addScaled(hb1.dir, -bsp * getBondLineShift(hb2.rightCos, hb2.rightSin));
}
var sgBondPath = getBondSingleStereoBoldPath(render, hb1, hb2, bond, struct);
return draw.bondDoubleStereoBold(render.paper, sgBondPath, b1, b2, render.options);
}
function getBondLineShift(cos, sin) {
if (sin < 0 || Math.abs(cos) > 0.9)
return 0;
return sin / (1 - cos);
}
function stereoUpBondGetCoordinates(hb, neihbid, bondSpace, struct) {
var neihb = struct.halfBonds.get(neihbid);
var cos = Vec2.dot(hb.dir, neihb.dir);
var sin = Vec2.cross(hb.dir, neihb.dir);
var cosHalf = Math.sqrt(0.5 * (1 - cos));
var biss = neihb.dir.rotateSC((sin >= 0 ? -1 : 1) * cosHalf, Math.sqrt(0.5 * (1 + cos)));
var denomAdd = 0.3;
var scale = 0.7;
var a1 = hb.p.addScaled(biss, scale * bondSpace / (cosHalf + denomAdd));
var a2 = hb.p.addScaled(biss.negated(), scale * bondSpace / (cosHalf + denomAdd));
return sin > 0 ? [a1, a2] : [a2, a1];
}
function getBondSingleDownPath(render, hb1, hb2) {
var a = hb1.p,
b = hb2.p;
var options = render.options;
var d = b.sub(a);
var len = d.length() + 0.2;
d = d.normalized();
var interval = 1.2 * options.lineWidth;
var nlines = Math.max(Math.floor((len - options.lineWidth) /
(options.lineWidth + interval)), 0) + 2;
var step = len / (nlines - 1);
return draw.bondSingleDown(render.paper, hb1, d, nlines, step, options);
}
function getBondSingleEitherPath(render, hb1, hb2) {
var a = hb1.p,
b = hb2.p;
var options = render.options;
var d = b.sub(a);
var len = d.length();
d = d.normalized();
var interval = 0.6 * options.lineWidth;
var nlines = Math.max(Math.floor((len - options.lineWidth) /
(options.lineWidth + interval)), 0) + 2;
var step = len / (nlines - 0.5);
return draw.bondSingleEither(render.paper, hb1, d, nlines, step, options);
}
function getBondDoublePath(render, hb1, hb2, bond, shiftA, shiftB) { // eslint-disable-line max-params, max-statements
var cisTrans = bond.b.stereo === Struct.Bond.PATTERN.STEREO.CIS_TRANS;
var a = hb1.p,
b = hb2.p,
n = hb1.norm,
shift = cisTrans ? 0 : bond.doubleBondShift;
var options = render.options;
var bsp = options.bondSpace / 2;
var s1 = bsp + (shift * bsp),
s2 = -bsp + (shift * bsp);
var a1 = a.addScaled(n, s1);
var b1 = b.addScaled(n, s1);
var a2 = a.addScaled(n, s2);
var b2 = b.addScaled(n, s2);
if (shift > 0) {
if (shiftA) {
a1 = a1.addScaled(hb1.dir, options.bondSpace *
getBondLineShift(hb1.rightCos, hb1.rightSin));
}
if (shiftB) {
b1 = b1.addScaled(hb1.dir, -options.bondSpace *
getBondLineShift(hb2.leftCos, hb2.leftSin));
}
} else if (shift < 0) {
if (shiftA) {
a2 = a2.addScaled(hb1.dir, options.bondSpace *
getBondLineShift(hb1.leftCos, hb1.leftSin));
}
if (shiftB) {
b2 = b2.addScaled(hb1.dir, -options.bondSpace *
getBondLineShift(hb2.rightCos, hb2.rightSin));
}
}
return draw.bondDouble(render.paper, a1, a2, b1, b2, cisTrans, options);
}
function getSingleOrDoublePath(render, hb1, hb2) {
var a = hb1.p,
b = hb2.p;
var options = render.options;
var nSect = (Vec2.dist(a, b) / (options.bondSpace + options.lineWidth)).toFixed() - 0;
if (!(nSect & 1))
nSect += 1;
return draw.bondSingleOrDouble(render.paper, hb1, hb2, nSect, options);
}
function getBondAromaticPath(render, hb1, hb2, bond, shiftA, shiftB) { // eslint-disable-line max-params
var dashdotPattern = [0.125, 0.125, 0.005, 0.125];
var mark = null,
dash = null;
var options = render.options;
var bondShift = bond.doubleBondShift;
if (bond.b.type == Struct.Bond.PATTERN.TYPE.SINGLE_OR_AROMATIC) {
mark = bondShift > 0 ? 1 : 2;
dash = dashdotPattern.map(function (v) {
return v * options.scale;
});
}
if (bond.b.type == Struct.Bond.PATTERN.TYPE.DOUBLE_OR_AROMATIC) {
mark = 3;
dash = dashdotPattern.map(function (v) {
return v * options.scale;
});
}
var paths = getAromaticBondPaths(hb1, hb2, bondShift, shiftA, shiftB, options.bondSpace, mark, dash);
return draw.bondAromatic(render.paper, paths, bondShift, options);
}
function getAromaticBondPaths(hb1, hb2, shift, shiftA, shiftB, bondSpace, mask, dash) { // eslint-disable-line max-params, max-statements
var a = hb1.p,
b = hb2.p,
n = hb1.norm;
var bsp = bondSpace / 2;
var s1 = bsp + (shift * bsp),
s2 = -bsp + (shift * bsp);
var a2 = a.addScaled(n, s1);
var b2 = b.addScaled(n, s1);
var a3 = a.addScaled(n, s2);
var b3 = b.addScaled(n, s2);
if (shift > 0) {
if (shiftA) {
a2 = a2.addScaled(hb1.dir, bondSpace *
getBondLineShift(hb1.rightCos, hb1.rightSin));
}
if (shiftB) {
b2 = b2.addScaled(hb1.dir, -bondSpace *
getBondLineShift(hb2.leftCos, hb2.leftSin));
}
} else if (shift < 0) {
if (shiftA) {
a3 = a3.addScaled(hb1.dir, bondSpace *
getBondLineShift(hb1.leftCos, hb1.leftSin));
}
if (shiftB) {
b3 = b3.addScaled(hb1.dir, -bondSpace *
getBondLineShift(hb2.rightCos, hb2.rightSin));
}
}
return draw.aromaticBondPaths(a2, a3, b2, b3, mask, dash);
}
function getReactingCenterPath(render, bond, hb1, hb2) { // eslint-disable-line max-statements
var a = hb1.p,
b = hb2.p;
var c = b.add(a).scaled(0.5);
var d = b.sub(a).normalized();
var n = d.rotateSC(1, 0);
var p = [];
var lw = render.options.lineWidth,
bs = render.options.bondSpace / 2;
var alongIntRc = lw, // half interval along for CENTER
alongIntMadeBroken = 2 * lw, // half interval between along for MADE_OR_BROKEN
alongSz = 1.5 * bs, // half size along for CENTER
acrossInt = 1.5 * bs, // half interval across for CENTER
acrossSz = 3.0 * bs, // half size across for all
tiltTan = 0.2; // tangent of the tilt angle
switch (bond.b.reactingCenterStatus) {
case Struct.Bond.PATTERN.REACTING_CENTER.NOT_CENTER: // X
p.push(c.addScaled(n, acrossSz).addScaled(d, tiltTan * acrossSz));
p.push(c.addScaled(n, -acrossSz).addScaled(d, -tiltTan * acrossSz));
p.push(c.addScaled(n, acrossSz).addScaled(d, -tiltTan * acrossSz));
p.push(c.addScaled(n, -acrossSz).addScaled(d, tiltTan * acrossSz));
break;
case Struct.Bond.PATTERN.REACTING_CENTER.CENTER: // #
p.push(c.addScaled(n, acrossSz).addScaled(d, tiltTan * acrossSz).addScaled(d, alongIntRc));
p.push(c.addScaled(n, -acrossSz).addScaled(d, -tiltTan * acrossSz).addScaled(d, alongIntRc));
p.push(c.addScaled(n, acrossSz).addScaled(d, tiltTan * acrossSz).addScaled(d, -alongIntRc));
p.push(c.addScaled(n, -acrossSz).addScaled(d, -tiltTan * acrossSz).addScaled(d, -alongIntRc));
p.push(c.addScaled(d, alongSz).addScaled(n, acrossInt));
p.push(c.addScaled(d, -alongSz).addScaled(n, acrossInt));
p.push(c.addScaled(d, alongSz).addScaled(n, -acrossInt));
p.push(c.addScaled(d, -alongSz).addScaled(n, -acrossInt));
break;
// case Bond.PATTERN.REACTING_CENTER.UNCHANGED: // o
// //draw a circle
// break;
case Struct.Bond.PATTERN.REACTING_CENTER.MADE_OR_BROKEN:
p.push(c.addScaled(n, acrossSz).addScaled(d, alongIntMadeBroken));
p.push(c.addScaled(n, -acrossSz).addScaled(d, alongIntMadeBroken));
p.push(c.addScaled(n, acrossSz).addScaled(d, -alongIntMadeBroken));
p.push(c.addScaled(n, -acrossSz).addScaled(d, -alongIntMadeBroken));
break;
case Struct.Bond.PATTERN.REACTING_CENTER.ORDER_CHANGED:
p.push(c.addScaled(n, acrossSz));
p.push(c.addScaled(n, -acrossSz));
break;
case Struct.Bond.PATTERN.REACTING_CENTER.MADE_OR_BROKEN_AND_CHANGED:
p.push(c.addScaled(n, acrossSz).addScaled(d, alongIntMadeBroken));
p.push(c.addScaled(n, -acrossSz).addScaled(d, alongIntMadeBroken));
p.push(c.addScaled(n, acrossSz).addScaled(d, -alongIntMadeBroken));
p.push(c.addScaled(n, -acrossSz).addScaled(d, -alongIntMadeBroken));
p.push(c.addScaled(n, acrossSz));
p.push(c.addScaled(n, -acrossSz));
break;
default:
return null;
}
return draw.reactingCenter(render.paper, p, render.options);
}
function getTopologyMark(render, bond, hb1, hb2) { // eslint-disable-line max-statements
var options = render.options;
var mark = null;
if (bond.b.topology == Struct.Bond.PATTERN.TOPOLOGY.RING)
mark = 'rng';
else if (bond.b.topology == Struct.Bond.PATTERN.TOPOLOGY.CHAIN)
mark = 'chn';
else
return null;
var a = hb1.p,
b = hb2.p;
var c = b.add(a).scaled(0.5);
var d = b.sub(a).normalized();
var n = d.rotateSC(1, 0);
var fixed = options.lineWidth;
if (bond.doubleBondShift > 0)
n = n.scaled(-bond.doubleBondShift);
else if (bond.doubleBondShift == 0)
fixed += options.bondSpace / 2;
var s = new Vec2(2, 1).scaled(options.bondSpace);
if (bond.b.type == Struct.Bond.PATTERN.TYPE.TRIPLE)
fixed += options.bondSpace;
var p = c.add(new Vec2(n.x * (s.x + fixed), n.y * (s.y + fixed)));
return draw.topologyMark(render.paper, p, mark, options);
}
function getIdsPath(bid, paper, hb1, hb2, bondIdxOff, param1, param2, norm) { // eslint-disable-line max-params
var pb = Vec2.lc(hb1.p, param1, hb2.p, param2, norm, bondIdxOff);
var ipath = paper.text(pb.x, pb.y, bid.toString());
var irbb = util.relBox(ipath.getBBox());
draw.recenterText(ipath, irbb);
return ipath;
}
/* ----- */
function setDoubleBondShift(bond, struct) {
var loop1, loop2;
loop1 = struct.halfBonds.get(bond.b.hb1).loop;
loop2 = struct.halfBonds.get(bond.b.hb2).loop;
if (loop1 >= 0 && loop2 >= 0) {
var d1 = struct.loops.get(loop1).dblBonds;
var d2 = struct.loops.get(loop2).dblBonds;
var n1 = struct.loops.get(loop1).hbs.length;
var n2 = struct.loops.get(loop2).hbs.length;
bond.doubleBondShift = selectDoubleBondShift(n1, n2, d1, d2);
} else if (loop1 >= 0) {
bond.doubleBondShift = -1;
} else if (loop2 >= 0) {
bond.doubleBondShift = 1;
} else {
bond.doubleBondShift = selectDoubleBondShiftChain(struct, bond);
}
}
function bondRecalc(bond, restruct, options) {
var render = restruct.render;
var atom1 = restruct.atoms.get(bond.b.begin);
var atom2 = restruct.atoms.get(bond.b.end);
var p1 = scale.obj2scaled(atom1.a.pp, render.options);
var p2 = scale.obj2scaled(atom2.a.pp, render.options);
var hb1 = restruct.molecule.halfBonds.get(bond.b.hb1);
var hb2 = restruct.molecule.halfBonds.get(bond.b.hb2);
hb1.p = shiftBondEnd(atom1, p1, hb1.dir, 2 * options.lineWidth);
hb2.p = shiftBondEnd(atom2, p2, hb2.dir, 2 * options.lineWidth);
bond.b.center = Vec2.lc2(atom1.a.pp, 0.5, atom2.a.pp, 0.5);
bond.b.len = Vec2.dist(p1, p2);
bond.b.sb = options.lineWidth * 5;
/* eslint-disable no-mixed-operators*/
bond.b.sa = Math.max(bond.b.sb, bond.b.len / 2 - options.lineWidth * 2);
/* eslint-enable no-mixed-operators*/
bond.b.angle = Math.atan2(hb1.dir.y, hb1.dir.x) * 180 / Math.PI;
}
function shiftBondEnd(atom, pos0, dir, margin) {
var t = 0;
var visel = atom.visel;
for (var k = 0; k < visel.exts.length; ++k) {
var box = visel.exts[k].translate(pos0);
t = Math.max(t, Vec2.shiftRayBox(pos0, dir, box));
}
if (t > 0)
pos0 = pos0.addScaled(dir, t + margin);
return pos0;
}
function selectDoubleBondShift(n1, n2, d1, d2) {
if (n1 == 6 && n2 != 6 && (d1 > 1 || d2 == 1))
return -1;
if (n2 == 6 && n1 != 6 && (d2 > 1 || d1 == 1))
return 1;
if (n2 * d1 > n1 * d2)
return -1;
if (n2 * d1 < n1 * d2)
return 1;
if (n2 > n1)
return -1;
return 1;
}
function selectDoubleBondShiftChain(struct, bond) {
var hb1 = struct.halfBonds.get(bond.b.hb1);
var hb2 = struct.halfBonds.get(bond.b.hb2);
var nLeft = (hb1.leftSin > 0.3 ? 1 : 0) + (hb2.rightSin > 0.3 ? 1 : 0);
var nRight = (hb2.leftSin > 0.3 ? 1 : 0) + (hb1.rightSin > 0.3 ? 1 : 0);
if (nLeft > nRight)
return -1;
if (nLeft < nRight)
return 1;
if ((hb1.leftSin > 0.3 ? 1 : 0) + (hb1.rightSin > 0.3 ? 1 : 0) == 1)
return 1;
return 0;
}
module.exports = ReBond;

View File

@ -0,0 +1,63 @@
/****************************************************************************
* Copyright 2017 EPAM Systems
*
* 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.
***************************************************************************/
var Box2Abs = require('../../util/box2abs');
var scale = require('../../util/scale');
var ReObject = require('./reobject');
function ReChiralFlag(pos) {
this.init('chiralFlag');
this.pp = pos;
}
ReChiralFlag.prototype = new ReObject();
ReChiralFlag.isSelectable = function () {
return true;
};
ReChiralFlag.prototype.highlightPath = function (render) {
var box = Box2Abs.fromRelBox(this.path.getBBox());
var sz = box.p1.sub(box.p0);
var p0 = box.p0.sub(render.options.offset);
return render.paper.rect(p0.x, p0.y, sz.x, sz.y);
};
ReChiralFlag.prototype.drawHighlight = function (render) {
var ret = this.highlightPath(render).attr(render.options.highlightStyle);
render.ctab.addReObjectPath('highlighting', this.visel, ret);
return ret;
};
ReChiralFlag.prototype.makeSelectionPlate = function (restruct, paper, options) {
return this.highlightPath(restruct.render).attr(options.selectionStyle);
};
ReChiralFlag.prototype.show = function (restruct, id, options) {
var render = restruct.render;
if (restruct.chiralFlagsChanged[id] <= 0) return;
var paper = render.paper;
var ps = scale.obj2scaled(this.pp, options);
this.path = paper.text(ps.x, ps.y, 'Chiral')
.attr({
'font': options.font,
'font-size': options.fontsz,
'fill': '#000'
});
render.ctab.addReObjectPath('data', this.visel, this.path, null, true);
};
module.exports = ReChiralFlag;

View File

@ -0,0 +1,48 @@
/****************************************************************************
* Copyright 2017 EPAM Systems
*
* 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.
***************************************************************************/
var ReObject = require('./reobject');
var scale = require('../../util/scale');
function ReDataSGroupData(sgroup) {
this.init('sgroupData');
this.sgroup = sgroup;
}
ReDataSGroupData.prototype = new ReObject();
ReDataSGroupData.isSelectable = function () {
return true;
};
ReDataSGroupData.prototype.highlightPath = function (render) {
var box = this.sgroup.dataArea;
var p0 = scale.obj2scaled(box.p0, render.options);
var sz = scale.obj2scaled(box.p1, render.options).sub(p0);
return render.paper.rect(p0.x, p0.y, sz.x, sz.y);
};
ReDataSGroupData.prototype.drawHighlight = function (render) {
var ret = this.highlightPath(render).attr(render.options.highlightStyle);
render.ctab.addReObjectPath('highlighting', this.visel, ret);
return ret;
};
ReDataSGroupData.prototype.makeSelectionPlate = function (restruct, paper, styles) { // TODO [MK] review parameters
return this.highlightPath(restruct.render).attr(styles.selectionStyle);
};
module.exports = ReDataSGroupData;

View File

@ -0,0 +1,112 @@
/****************************************************************************
* Copyright 2017 EPAM Systems
*
* 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.
***************************************************************************/
var Box2Abs = require('../../util/box2abs');
var Vec2 = require('../../util/vec2');
var ReObject = require('./reobject');
var scale = require('../../util/scale');
function ReFrag(/* Struct.Fragment = {}*/frag) {
this.init('frag');
this.item = frag;
}
ReFrag.prototype = new ReObject();
ReFrag.isSelectable = function () {
return false;
};
ReFrag.prototype.fragGetAtoms = function (render, fid) {
var ret = [];
render.ctab.atoms.each(function (aid, atom) {
if (atom.a.fragment == fid)
ret.push(aid);
}, this);
return ret;
};
ReFrag.prototype.fragGetBonds = function (render, fid) {
var ret = [];
render.ctab.bonds.each(function (bid, bond) {
if (render.ctab.atoms.get(bond.b.begin).a.fragment == fid &&
render.ctab.atoms.get(bond.b.end).a.fragment == fid)
ret.push(bid);
}, this);
return ret;
};
ReFrag.prototype.calcBBox = function (restruct, fid, render) { // TODO need to review parameter list
var ret;
restruct.atoms.each(function (aid, atom) {
if (atom.a.fragment == fid) {
// TODO ReObject.calcBBox to be used instead
var bba = atom.visel.boundingBox;
if (!bba) {
bba = new Box2Abs(atom.a.pp, atom.a.pp);
var ext = new Vec2(0.05 * 3, 0.05 * 3);
bba = bba.extend(ext, ext);
} else {
if (!render) {
console.warn('No boundingBox fragment precalc');
render = global._ui_editor.render; // eslint-disable-line
}
bba = bba.translate((render.options.offset || new Vec2()).negated()).transform(scale.scaled2obj, render.options);
}
ret = (ret ? Box2Abs.union(ret, bba) : bba);
}
});
return ret;
};
// TODO need to review parameter list
ReFrag.prototype._draw = function (render, fid, attrs) { // eslint-disable-line no-underscore-dangle
var bb = this.calcBBox(render.ctab, fid, render);
if (bb) {
var p0 = scale.obj2scaled(new Vec2(bb.p0.x, bb.p0.y), render.options);
var p1 = scale.obj2scaled(new Vec2(bb.p1.x, bb.p1.y), render.options);
return render.paper.rect(p0.x, p0.y, p1.x - p0.x, p1.y - p0.y, 0).attr(attrs);
} else { // eslint-disable-line no-else-return
// TODO abnormal situation, empty fragments must be destroyed by tools
console.assert(null, 'Empty fragment');
}
};
ReFrag.prototype.draw = function (render) { // eslint-disable-line no-unused-vars
return null;// this._draw(render, fid, { 'stroke' : 'lightgray' }); // [RB] for debugging only
};
ReFrag.prototype.drawHighlight = function (render) { // eslint-disable-line no-unused-vars
// Do nothing. This method shouldn't actually be called.
};
ReFrag.prototype.setHighlight = function (highLight, render) {
var fid = render.ctab.frags.keyOf(this);
if (!(typeof fid === "undefined")) {
render.ctab.atoms.each(function (aid, atom) {
if (atom.a.fragment == fid)
atom.setHighlight(highLight, render);
}, this);
render.ctab.bonds.each(function (bid, bond) {
if (render.ctab.atoms.get(bond.b.begin).a.fragment == fid)
bond.setHighlight(highLight, render);
}, this);
} else {
// TODO abnormal situation, fragment does not belong to the render
}
};
module.exports = ReFrag;

View File

@ -0,0 +1,124 @@
/****************************************************************************
* Copyright 2017 EPAM Systems
*
* 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.
***************************************************************************/
var Vec2 = require('../../util/vec2');
var Visel = require('./visel');
var ReObject = require('./reobject');
var scale = require('../../util/scale');
var util = require('../util');
var Struct = require('../../chem/struct');
var tfx = util.tfx;
function ReLoop(loop) {
this.loop = loop;
this.visel = new Visel('loop');
this.centre = new Vec2();
this.radius = new Vec2();
}
ReLoop.prototype = new ReObject();
ReLoop.isSelectable = function () {
return false;
};
ReLoop.prototype.show = function (restruct, rlid, options) { // eslint-disable-line max-statements
var render = restruct.render;
var paper = render.paper;
var molecule = restruct.molecule;
var loop = this.loop;
this.centre = new Vec2();
loop.hbs.forEach(function (hbid) {
var hb = molecule.halfBonds.get(hbid);
var bond = restruct.bonds.get(hb.bid);
var apos = scale.obj2scaled(restruct.atoms.get(hb.begin).a.pp, options);
if (bond.b.type !== Struct.Bond.PATTERN.TYPE.AROMATIC)
loop.aromatic = false;
this.centre.add_(apos); // eslint-disable-line no-underscore-dangle
}, this);
loop.convex = true;
for (var k = 0; k < this.loop.hbs.length; ++k) {
var hba = molecule.halfBonds.get(loop.hbs[k]);
var hbb = molecule.halfBonds.get(loop.hbs[(k + 1) % loop.hbs.length]);
var angle = Math.atan2(
Vec2.cross(hba.dir, hbb.dir),
Vec2.dot(hba.dir, hbb.dir));
if (angle > 0)
loop.convex = false;
}
this.centre = this.centre.scaled(1.0 / loop.hbs.length);
this.radius = -1;
loop.hbs.forEach(function (hbid) {
var hb = molecule.halfBonds.get(hbid);
var apos = scale.obj2scaled(restruct.atoms.get(hb.begin).a.pp, options);
var bpos = scale.obj2scaled(restruct.atoms.get(hb.end).a.pp, options);
var n = Vec2.diff(bpos, apos).rotateSC(1, 0).normalized();
var dist = Vec2.dot(Vec2.diff(apos, this.centre), n);
this.radius = (this.radius < 0) ? dist : Math.min(this.radius, dist);
}, this);
this.radius *= 0.7;
if (!loop.aromatic)
return;
var path = null;
if (loop.convex && options.aromaticCircle) {
path = paper.circle(this.centre.x, this.centre.y, this.radius)
.attr({
'stroke': '#000',
'stroke-width': options.lineattr['stroke-width']
});
} else {
var pathStr = '';
for (k = 0; k < loop.hbs.length; ++k) {
hba = molecule.halfBonds.get(loop.hbs[k]);
hbb = molecule.halfBonds.get(loop.hbs[(k + 1) % loop.hbs.length]);
angle = Math.atan2(
Vec2.cross(hba.dir, hbb.dir),
Vec2.dot(hba.dir, hbb.dir));
var halfAngle = (Math.PI - angle) / 2;
var dir = hbb.dir.rotate(halfAngle);
var pi = scale.obj2scaled(restruct.atoms.get(hbb.begin).a.pp, options);
var sin = Math.sin(halfAngle);
var minSin = 0.1;
if (Math.abs(sin) < minSin)
sin = sin * minSin / Math.abs(sin);
var offset = options.bondSpace / sin;
var qi = pi.addScaled(dir, -offset);
pathStr += (k === 0 ? 'M' : 'L');
pathStr += tfx(qi.x) + ',' + tfx(qi.y);
}
pathStr += 'Z';
path = paper.path(pathStr)
.attr({
'stroke': '#000',
'stroke-width': options.lineattr['stroke-width'],
'stroke-dasharray': '- '
});
}
restruct.addReObjectPath('data', this.visel, path, null, true);
};
ReLoop.prototype.isValid = function (struct, rlid) {
var halfBonds = struct.halfBonds;
var loop = this.loop;
var bad = false;
loop.hbs.forEach(function (hbid) {
if (!halfBonds.has(hbid) || halfBonds.get(hbid).loop !== rlid)
bad = true;
}, this);
return !bad;
};
module.exports = ReLoop;

View File

@ -0,0 +1,75 @@
/****************************************************************************
* Copyright 2017 EPAM Systems
*
* 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.
***************************************************************************/
var Visel = require('./visel');
var scale = require('../../util/scale');
function ReObject() {
}
ReObject.prototype.init = function (viselType) {
this.visel = new Visel(viselType);
this.highlight = false;
this.highlighting = null;
this.selected = false;
this.selectionPlate = null;
};
// returns the bounding box of a ReObject in the object coordinates
ReObject.prototype.getVBoxObj = function (render) {
var vbox = this.visel.boundingBox;
if (vbox === null)
return null;
if (render.options.offset)
vbox = vbox.translate(render.options.offset.negated());
return vbox.transform(scale.scaled2obj, render.options);
};
ReObject.prototype.setHighlight = function (highLight, render) { // TODO render should be field
if (highLight) {
var noredraw = 'highlighting' in this && this.highlighting != null;// && !this.highlighting.removed;
if (noredraw) {
if (this.highlighting.type == 'set')
noredraw = !this.highlighting[0].removed;
else
noredraw = !this.highlighting.removed;
}
if (noredraw) {
this.highlighting.show();
} else {
render.paper.setStart();
this.drawHighlight(render);
this.highlighting = render.paper.setFinish();
}
} else
if (this.highlighting) {
this.highlighting.hide();
}
this.highlight = highLight;
};
ReObject.prototype.drawHighlight = function () {
console.assert('ReObject.drawHighlight is not overridden'); // eslint-disable-line no-console
};
ReObject.prototype.makeSelectionPlate = function () {
console.assert(null, 'ReObject.makeSelectionPlate is not overridden'); // eslint-disable-line no-console
};
module.exports = ReObject;

View File

@ -0,0 +1,198 @@
/****************************************************************************
* Copyright 2017 EPAM Systems
*
* 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.
***************************************************************************/
var Box2Abs = require('../../util/box2abs');
var Vec2 = require('../../util/vec2');
var util = require('../util');
var draw = require('../draw');
var scale = require('../../util/scale');
var ReObject = require('./reobject');
var BORDER_EXT = new Vec2(0.05 * 3, 0.05 * 3);
function ReRGroup(/* Struct.RGroup*/rgroup) {
this.init('rgroup');
this.labelBox = null;
this.item = rgroup;
}
ReRGroup.prototype = new ReObject();
ReRGroup.isSelectable = function () {
return false;
};
ReRGroup.prototype.getAtoms = function (render) {
var ret = [];
this.item.frags.each(function (fnum, fid) {
ret = ret.concat(render.ctab.frags.get(fid).fragGetAtoms(render, fid));
});
return ret;
};
ReRGroup.prototype.getBonds = function (render) {
var ret = [];
this.item.frags.each(function (fnum, fid) {
ret = ret.concat(render.ctab.frags.get(fid).fragGetBonds(render, fid));
});
return ret;
};
ReRGroup.prototype.calcBBox = function (render) {
var ret;
this.item.frags.each(function (fnum, fid) {
var bbf = render.ctab.frags.get(fid).calcBBox(render.ctab, fid, render);
if (bbf)
ret = (ret ? Box2Abs.union(ret, bbf) : bbf);
});
ret = ret.extend(BORDER_EXT, BORDER_EXT); // eslint-disable-line no-underscore-dangle
return ret;
};
function rGroupdrawBrackets(set, render, bb, d) {
d = scale.obj2scaled(d || new Vec2(1, 0), render.options);
var bracketWidth = Math.min(0.25, bb.sz().x * 0.3);
var bracketHeight = bb.p1.y - bb.p0.y;
var cy = 0.5 * (bb.p1.y + bb.p0.y);
var leftBracket = draw.bracket(render.paper, d.negated(),
d.negated().rotateSC(1, 0),
scale.obj2scaled(new Vec2(bb.p0.x, cy), render.options),
bracketWidth, bracketHeight, render.options);
var rightBracket = draw.bracket(render.paper, d, d.rotateSC(1, 0),
scale.obj2scaled(new Vec2(bb.p1.x, cy), render.options),
bracketWidth, bracketHeight, render.options);
return set.push(leftBracket, rightBracket);
}
// TODO need to review parameter list
ReRGroup.prototype.draw = function (render, options) { // eslint-disable-line max-statements
var bb = this.calcBBox(render);
if (bb) {
var ret = { data: [] };
var p0 = scale.obj2scaled(bb.p0, options);
var p1 = scale.obj2scaled(bb.p1, options);
var brackets = render.paper.set();
rGroupdrawBrackets(brackets, render, bb); // eslint-disable-line new-cap
ret.data.push(brackets);
var key = render.ctab.rgroups.keyOf(this);
var labelSet = render.paper.set();
var label = render.paper.text(p0.x, (p0.y + p1.y) / 2, 'R' + key + '=')
.attr({
'font': options.font,
'font-size': options.fontRLabel,
'fill': 'black'
});
var labelBox = util.relBox(label.getBBox());
/* eslint-disable no-mixed-operators*/
label.translateAbs(-labelBox.width / 2 - options.lineWidth, 0);
/* eslint-enable no-mixed-operators*/
labelSet.push(label);
var logicStyle = {
'font': options.font,
'font-size': options.fontRLogic,
'fill': 'black'
};
var logic = [];
// TODO [RB] temporary solution, need to review
// BEGIN
/*
if (this.item.range.length > 0)
logic.push(this.item.range);
if (this.item.resth)
logic.push("RestH");
if (this.item.ifthen > 0)
logic.push("IF R" + key.toString() + " THEN R" + this.item.ifthen.toString());
*/
logic.push(
(this.item.ifthen > 0 ? 'IF ' : '') +
'R' + key.toString() +
/* eslint-disable no-nested-ternary */
(this.item.range.length > 0 ?
this.item.range.startsWith('>') || this.item.range.startsWith('<') || this.item.range.startsWith('=') ?
this.item.range : '=' + this.item.range : '>0') +
(this.item.resth ? ' (RestH)' : '') +
(this.item.ifthen > 0 ? '\nTHEN R' + this.item.ifthen.toString() : '')
/* eslint-enable no-nested-ternary */
);
// END
/* eslint-disable no-mixed-operators*/
var shift = labelBox.height / 2 + options.lineWidth / 2;
/* eslint-enable no-mixed-operators*/
for (var i = 0; i < logic.length; ++i) {
var logicPath = render.paper.text(p0.x, (p0.y + p1.y) / 2, logic[i]).attr(logicStyle);
var logicBox = util.relBox(logicPath.getBBox());
shift += logicBox.height / 2;
/* eslint-disable no-mixed-operators*/
logicPath.translateAbs(-logicBox.width / 2 - 6 * options.lineWidth, shift);
shift += logicBox.height / 2 + options.lineWidth / 2;
/* eslint-enable no-mixed-operators*/
ret.data.push(logicPath);
labelSet.push(logicPath);
}
ret.data.push(label);
this.labelBox = Box2Abs.fromRelBox(labelSet.getBBox()).transform(scale.scaled2obj, render.options);
return ret;
} else { // eslint-disable-line no-else-return
// TODO abnormal situation, empty fragments must be destroyed by tools
return {};
}
};
// TODO need to review parameter list
ReRGroup.prototype._draw = function (render, rgid, attrs) { // eslint-disable-line no-underscore-dangle
var bb = this.getVBoxObj(render).extend(BORDER_EXT, BORDER_EXT); // eslint-disable-line no-underscore-dangle
if (bb) {
var p0 = scale.obj2scaled(bb.p0, render.options);
var p1 = scale.obj2scaled(bb.p1, render.options);
return render.paper.rect(p0.x, p0.y, p1.x - p0.x, p1.y - p0.y, 0).attr(attrs);
}
};
ReRGroup.prototype.drawHighlight = function (render) {
var rgid = render.ctab.rgroups.keyOf(this);
if (!(typeof rgid === 'undefined')) {
var ret = this._draw(render, rgid, render.options.highlightStyle/* { 'fill' : 'red' }*/); // eslint-disable-line no-underscore-dangle
render.ctab.addReObjectPath('highlighting', this.visel, ret);
/*
this.getAtoms(render).each(function(aid) {
render.ctab.atoms.get(aid).drawHighlight(render);
}, this);
*/
this.item.frags.each(function (fnum, fid) {
render.ctab.frags.get(fid).drawHighlight(render);
}, this);
return ret;
} else { // eslint-disable-line no-else-return
// TODO abnormal situation, fragment does not belong to the render
}
};
ReRGroup.prototype.show = function (restruct, id, options) {
var drawing = this.draw(restruct.render, options);
for (var group in drawing) {
if (drawing.hasOwnProperty(group)) {
while (drawing[group].length > 0)
restruct.addReObjectPath(group, this.visel, drawing[group].shift(), null, true);
}
}
// TODO rgroup selection & highlighting
};
module.exports = ReRGroup;

View File

@ -0,0 +1,63 @@
/****************************************************************************
* Copyright 2017 EPAM Systems
*
* 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.
***************************************************************************/
var ReObject = require('./reobject');
var Box2Abs = require('../../util/box2abs');
var Vec2 = require('../../util/vec2');
var draw = require('../draw');
var util = require('../util');
var scale = require('../../util/scale');
function ReRxnArrow(/* chem.RxnArrow*/arrow) {
this.init('rxnArrow');
this.item = arrow;
}
ReRxnArrow.prototype = new ReObject();
ReRxnArrow.isSelectable = function () {
return true;
};
ReRxnArrow.prototype.highlightPath = function (render) {
var p = scale.obj2scaled(this.item.pp, render.options);
var s = render.options.scale;
return render.paper.rect(p.x - s, p.y - s / 4, 2 * s, s / 2, s / 8); // eslint-disable-line no-mixed-operators
};
ReRxnArrow.prototype.drawHighlight = function (render) {
var ret = this.highlightPath(render).attr(render.options.highlightStyle);
render.ctab.addReObjectPath('highlighting', this.visel, ret);
return ret;
};
ReRxnArrow.prototype.makeSelectionPlate = function (restruct, paper, styles) {
return this.highlightPath(restruct.render).attr(styles.selectionStyle);
};
ReRxnArrow.prototype.show = function (restruct, id, options) {
var render = restruct.render;
var centre = scale.obj2scaled(this.item.pp, options);
var path = draw.arrow(render.paper,
new Vec2(centre.x - options.scale, centre.y),
new Vec2(centre.x + options.scale, centre.y),
options);
var offset = options.offset;
if (offset != null)
path.translateAbs(offset.x, offset.y);
this.visel.add(path, Box2Abs.fromRelBox(util.relBox(path.getBBox())));
};
module.exports = ReRxnArrow;

View File

@ -0,0 +1,61 @@
/****************************************************************************
* Copyright 2017 EPAM Systems
*
* 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.
***************************************************************************/
var ReObject = require('./reobject');
var Box2Abs = require('../../util/box2abs');
var draw = require('../draw');
var util = require('../util');
var scale = require('../../util/scale');
function ReRxnPlus(/* chem.RxnPlus*/plus) {
this.init('rxnPlus');
this.item = plus;
}
ReRxnPlus.prototype = new ReObject();
ReRxnPlus.isSelectable = function () {
return true;
};
ReRxnPlus.prototype.highlightPath = function (render) {
var p = scale.obj2scaled(this.item.pp, render.options);
var s = render.options.scale;
/* eslint-disable no-mixed-operators*/
return render.paper.rect(p.x - s / 4, p.y - s / 4, s / 2, s / 2, s / 8);
/* eslint-enable no-mixed-operators*/
};
ReRxnPlus.prototype.drawHighlight = function (render) {
var ret = this.highlightPath(render).attr(render.options.highlightStyle);
render.ctab.addReObjectPath('highlighting', this.visel, ret);
return ret;
};
ReRxnPlus.prototype.makeSelectionPlate = function (restruct, paper, styles) { // TODO [MK] review parameters
return this.highlightPath(restruct.render).attr(styles.selectionStyle);
};
ReRxnPlus.prototype.show = function (restruct, id, options) {
var render = restruct.render;
var centre = scale.obj2scaled(this.item.pp, options);
var path = draw.plus(render.paper, centre, options);
var offset = options.offset;
if (offset != null)
path.translateAbs(offset.x, offset.y);
this.visel.add(path, Box2Abs.fromRelBox(util.relBox(path.getBBox())));
};
module.exports = ReRxnPlus;

View File

@ -0,0 +1,378 @@
/****************************************************************************
* Copyright 2017 EPAM Systems
*
* 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.
***************************************************************************/
var Box2Abs = require('../../util/box2abs');
var Set = require('../../util/set');
var Vec2 = require('../../util/vec2');
var util = require('../util');
var scale = require('../../util/scale');
var Struct = require('../../chem/struct');
var draw = require('../draw');
var ReDataSGroupData = require('./redatasgroupdata');
var ReObject = require('./reobject');
var tfx = util.tfx;
function ReSGroup(sgroup) {
this.init('sgroup');
this.item = sgroup;
}
ReSGroup.prototype = new ReObject();
ReSGroup.isSelectable = function () {
return false;
};
ReSGroup.prototype.draw = function (remol, sgroup) {
var render = remol.render;
var set = render.paper.set();
var inBonds = [],
xBonds = [];
var atomSet = Set.fromList(sgroup.atoms);
Struct.SGroup.getCrossBonds(inBonds, xBonds, remol.molecule, atomSet);
bracketPos(sgroup, render, remol.molecule, xBonds);
var bb = sgroup.bracketBox;
var d = sgroup.bracketDir;
sgroup.areas = [bb];
switch (sgroup.type) {
case 'MUL':
new SGroupdrawBrackets(set, render, sgroup, xBonds, atomSet, bb, d, sgroup.data.mul);
break;
case 'SRU':
var connectivity = sgroup.data.connectivity || 'eu';
if (connectivity == 'ht')
connectivity = '';
var subscript = sgroup.data.subscript || 'n';
new SGroupdrawBrackets(set, render, sgroup, xBonds, atomSet, bb, d, subscript, connectivity);
break;
case 'SUP':
new SGroupdrawBrackets(set, render, sgroup, xBonds, atomSet, bb, d, sgroup.data.name, null, { 'font-style': 'italic' });
break;
case 'GEN':
new SGroupdrawBrackets(set, render, sgroup, xBonds, atomSet, bb, d);
break;
case 'DAT':
set = drawGroupDat(remol, sgroup);
break;
default: break;
}
return set;
};
function SGroupdrawBrackets(set, render, sg, xbonds, atomSet, bb, d, lowerIndexText, upperIndexText, indexAttribute) { // eslint-disable-line max-params
var brackets = getBracketParameters(render.ctab.molecule, xbonds, atomSet, bb, d, render, sg.id);
var ir = -1;
for (var i = 0; i < brackets.length; ++i) {
var bracket = brackets[i];
var path = draw.bracket(render.paper, scale.obj2scaled(bracket.d, render.options),
scale.obj2scaled(bracket.n, render.options),
scale.obj2scaled(bracket.c, render.options),
bracket.w, bracket.h, render.options);
set.push(path);
if (ir < 0 || brackets[ir].d.x < bracket.d.x || (brackets[ir].d.x == bracket.d.x && brackets[ir].d.y > bracket.d.y))
ir = i;
}
var bracketR = brackets[ir];
function renderIndex(text, shift) {
var indexPos = scale.obj2scaled(bracketR.c.addScaled(bracketR.n, shift * bracketR.h), render.options);
var indexPath = render.paper.text(indexPos.x, indexPos.y, text)
.attr({
'font': render.options.font,
'font-size': render.options.fontszsub
});
if (indexAttribute)
indexPath.attr(indexAttribute);
var indexBox = Box2Abs.fromRelBox(util.relBox(indexPath.getBBox()));
var t = Math.max(Vec2.shiftRayBox(indexPos, bracketR.d.negated(), indexBox), 3) + 2;
indexPath.translateAbs(t * bracketR.d.x, t * bracketR.d.y);
set.push(indexPath);
}
if (lowerIndexText)
renderIndex(lowerIndexText, 0.5);
if (upperIndexText)
renderIndex(upperIndexText, -0.5);
}
function showValue(paper, pos, sg, options) {
var text = paper.text(pos.x, pos.y, sg.data.fieldValue)
.attr({
'font': options.font,
'font-size': options.fontsz
});
var box = text.getBBox();
var rect = paper.rect(box.x - 1, box.y - 1, box.width + 2, box.height + 2, 3, 3);
rect = sg.selected ?
rect.attr(options.selectionStyle) :
rect.attr({ fill: '#fff', stroke: '#fff' });
var st = paper.set();
st.push(
rect,
text.toFront()
);
return st;
}
function drawGroupDat(restruct, sgroup) {
const render = restruct.render;
// NB: we did not pass xbonds parameter to the backetPos method above,
// so the result will be in the regular coordinate system
bracketPos(sgroup, render, restruct.molecule);
sgroup.areas = sgroup.bracketBox ? [sgroup.bracketBox] : [];
if (sgroup.pp === null)
sgroup.pp = definePP(restruct, sgroup);
return sgroup.data.attached ? drawAttachedDat(restruct, sgroup) : drawAbsoluteDat(restruct, sgroup);
}
function definePP(restruct, sgroup) {
let topLeftPoint = sgroup.bracketBox.p1.add(new Vec2(0.5, 0.5));
const sgroups = restruct.molecule.sgroups.values();
for (let i = 0; i < restruct.molecule.sgroups.count(); ++i) {
if (!descriptorIntersects(sgroups, topLeftPoint))
break;
topLeftPoint = topLeftPoint.add(new Vec2(0, 0.5));
}
return topLeftPoint;
}
function descriptorIntersects(sgroups, topLeftPoint) {
return sgroups.some(sg => {
if (!sg.pp)
return false;
const sgBottomRightPoint = sg.pp.add(new Vec2(0.5, 0.5));
const bottomRightPoint = topLeftPoint.add(new Vec2(0.5, 0.5));
return Vec2.segmentIntersection(sg.pp, sgBottomRightPoint, topLeftPoint, bottomRightPoint);
});
}
function drawAbsoluteDat(restruct, sgroup) {
const render = restruct.render;
const options = render.options;
const paper = render.paper;
const set = paper.set();
const ps = sgroup.pp.scaled(options.scale);
const name = showValue(paper, ps, sgroup, options);
const box = util.relBox(name.getBBox());
name.translateAbs(0.5 * box.width, -0.5 * box.height);
set.push(name);
const sbox = Box2Abs.fromRelBox(util.relBox(name.getBBox()));
sgroup.dataArea = sbox.transform(scale.scaled2obj, render.options);
if (!restruct.sgroupData.has(sgroup.id))
restruct.sgroupData.set(sgroup.id, new ReDataSGroupData(sgroup));
return set;
}
function drawAttachedDat(restruct, sgroup) {
const render = restruct.render;
const options = render.options;
const paper = render.paper;
const set = paper.set();
Struct.SGroup.getAtoms(restruct, sgroup).forEach(aid => {
const atom = restruct.atoms.get(aid);
const p = scale.obj2scaled(atom.a.pp, options);
const bb = atom.visel.boundingBox;
if (bb !== null)
p.x = Math.max(p.x, bb.p1.x);
p.x += options.lineWidth; // shift a bit to the right
const nameI = showValue(paper, p, sgroup, options);
const boxI = util.relBox(nameI.getBBox());
nameI.translateAbs(0.5 * boxI.width, -0.3 * boxI.height);
set.push(nameI);
let sboxI = Box2Abs.fromRelBox(util.relBox(nameI.getBBox()));
sboxI = sboxI.transform(scale.scaled2obj, render.options);
sgroup.areas.push(sboxI);
});
return set;
}
function bracketPos(sg, render, mol, xbonds) { // eslint-disable-line max-statements
var atoms = sg.atoms;
if (!xbonds || xbonds.length !== 2) {
sg.bracketDir = new Vec2(1, 0);
} else {
var p1 = mol.bonds.get(xbonds[0]).getCenter(mol);
var p2 = mol.bonds.get(xbonds[1]).getCenter(mol);
sg.bracketDir = Vec2.diff(p2, p1).normalized();
}
var d = sg.bracketDir;
var bb = null;
var contentBoxes = [];
atoms.forEach(function (aid) {
var atom = mol.atoms.get(aid);
var bba = render ? render.ctab.atoms.get(aid).visel.boundingBox : null;
if (!bba) {
var pos = new Vec2(atom.pp);
var ext = new Vec2(0.05 * 3, 0.05 * 3);
bba = new Box2Abs(pos, pos).extend(ext, ext);
} else {
bba = bba.translate((render.options.offset || new Vec2()).negated()).transform(scale.scaled2obj, render.options);
}
contentBoxes.push(bba);
}, this);
mol.sGroupForest.children.get(sg.id).forEach(function (sgid) {
var bba = render.ctab.sgroups.get(sgid).visel.boundingBox;
bba = bba.translate((render.options.offset || new Vec2()).negated()).transform(scale.scaled2obj, render.options);
contentBoxes.push(bba);
}, this);
contentBoxes.forEach(function (bba) {
var bbb = null;
[bba.p0.x, bba.p1.x].forEach(function (x) {
[bba.p0.y, bba.p1.y].forEach(function (y) {
var v = new Vec2(x, y);
var p = new Vec2(Vec2.dot(v, d), Vec2.dot(v, d.rotateSC(1, 0)));
bbb = (bbb === null) ? new Box2Abs(p, p) : bbb.include(p);
}, this);
}, this);
bb = (bb === null) ? bbb : Box2Abs.union(bb, bbb);
}, this);
var vext = new Vec2(0.2, 0.4);
if (bb !== null) bb = bb.extend(vext, vext);
sg.bracketBox = bb;
}
function getBracketParameters(mol, xbonds, atomSet, bb, d, render, id) { // eslint-disable-line max-params
function BracketParams(c, d, w, h) {
this.c = c;
this.d = d;
this.n = d.rotateSC(1, 0);
this.w = w;
this.h = h;
}
var brackets = [];
var n = d.rotateSC(1, 0);
if (xbonds.length < 2) {
(function () {
d = d || new Vec2(1, 0);
n = n || d.rotateSC(1, 0);
var bracketWidth = Math.min(0.25, bb.sz().x * 0.3);
var cl = Vec2.lc2(d, bb.p0.x, n, 0.5 * (bb.p0.y + bb.p1.y));
var cr = Vec2.lc2(d, bb.p1.x, n, 0.5 * (bb.p0.y + bb.p1.y));
var bracketHeight = bb.sz().y;
brackets.push(new BracketParams(cl, d.negated(), bracketWidth, bracketHeight), new BracketParams(cr, d, bracketWidth, bracketHeight));
})();
} else if (xbonds.length === 2) {
(function () { // eslint-disable-line max-statements
var b1 = mol.bonds.get(xbonds[0]);
var b2 = mol.bonds.get(xbonds[1]);
var cl0 = b1.getCenter(mol);
var cr0 = b2.getCenter(mol);
var tl = -1;
var tr = -1;
var tt = -1;
var tb = -1;
var cc = Vec2.centre(cl0, cr0);
var dr = Vec2.diff(cr0, cl0).normalized();
var dl = dr.negated();
var dt = dr.rotateSC(1, 0);
var db = dt.negated();
mol.sGroupForest.children.get(id).forEach(function (sgid) {
var bba = render.ctab.sgroups.get(sgid).visel.boundingBox;
bba = bba.translate((render.options.offset || new Vec2()).negated()).transform(scale.scaled2obj, render.options);
tl = Math.max(tl, Vec2.shiftRayBox(cl0, dl, bba));
tr = Math.max(tr, Vec2.shiftRayBox(cr0, dr, bba));
tt = Math.max(tt, Vec2.shiftRayBox(cc, dt, bba));
tb = Math.max(tb, Vec2.shiftRayBox(cc, db, bba));
}, this);
tl = Math.max(tl + 0.2, 0);
tr = Math.max(tr + 0.2, 0);
tt = Math.max(Math.max(tt, tb) + 0.1, 0);
var bracketWidth = 0.25;
var bracketHeight = 1.5 + tt;
brackets.push(new BracketParams(cl0.addScaled(dl, tl), dl, bracketWidth, bracketHeight),
new BracketParams(cr0.addScaled(dr, tr), dr, bracketWidth, bracketHeight));
})();
} else {
(function () {
for (var i = 0; i < xbonds.length; ++i) {
var b = mol.bonds.get(xbonds[i]);
var c = b.getCenter(mol);
var d = Set.contains(atomSet, b.begin) ? b.getDir(mol) : b.getDir(mol).negated();
brackets.push(new BracketParams(c, d, 0.2, 1.0));
}
})();
}
return brackets;
}
ReSGroup.prototype.drawHighlight = function (render) { // eslint-disable-line max-statements
var options = render.options;
var paper = render.paper;
var sg = this.item;
var bb = sg.bracketBox.transform(scale.obj2scaled, options);
var lw = options.lineWidth;
var vext = new Vec2(lw * 4, lw * 6);
bb = bb.extend(vext, vext);
var d = sg.bracketDir,
n = d.rotateSC(1, 0);
var a0 = Vec2.lc2(d, bb.p0.x, n, bb.p0.y);
var a1 = Vec2.lc2(d, bb.p0.x, n, bb.p1.y);
var b0 = Vec2.lc2(d, bb.p1.x, n, bb.p0.y);
var b1 = Vec2.lc2(d, bb.p1.x, n, bb.p1.y);
var set = paper.set();
sg.highlighting = paper
.path('M{0},{1}L{2},{3}L{4},{5}L{6},{7}L{0},{1}', tfx(a0.x), tfx(a0.y), tfx(a1.x), tfx(a1.y), tfx(b1.x), tfx(b1.y), tfx(b0.x), tfx(b0.y))
.attr(options.highlightStyle);
set.push(sg.highlighting);
Struct.SGroup.getAtoms(render.ctab.molecule, sg).forEach(function (aid) {
set.push(render.ctab.atoms.get(aid).makeHighlightPlate(render));
}, this);
Struct.SGroup.getBonds(render.ctab.molecule, sg).forEach(function (bid) {
set.push(render.ctab.bonds.get(bid).makeHighlightPlate(render));
}, this);
render.ctab.addReObjectPath('highlighting', this.visel, set);
};
ReSGroup.prototype.show = function (restruct) {
var render = restruct.render;
var sgroup = this.item;
if (sgroup.data.fieldName !== "MRV_IMPLICIT_H") {
var remol = render.ctab;
var path = this.draw(remol, sgroup);
restruct.addReObjectPath('data', this.visel, path, null, true);
this.setHighlight(this.highlight, render); // TODO: fix this
}
};
module.exports = ReSGroup;

View File

@ -0,0 +1,62 @@
/****************************************************************************
* Copyright 2017 EPAM Systems
*
* 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.
***************************************************************************/
// Visel is a shorthand for VISual ELement
// It corresponds to a visualization (i.e. set of paths) of an atom or a bond.
var Box2Abs = require('../../util/box2abs');
var Vec2 = require('../../util/vec2');
function Visel(type) {
this.type = type;
this.paths = [];
this.boxes = [];
this.boundingBox = null;
}
Visel.prototype.add = function (path, bb, ext) {
this.paths.push(path);
if (bb) {
this.boxes.push(bb);
this.boundingBox = this.boundingBox == null ? bb : Box2Abs.union(this.boundingBox, bb);
}
if (ext)
this.exts.push(ext);
};
Visel.prototype.clear = function () {
this.paths = [];
this.boxes = [];
this.exts = [];
this.boundingBox = null;
};
Visel.prototype.translate = function (x, y) {
if (arguments.length > 2) // TODO: replace to debug time assert
throw new Error('One vector or two scalar arguments expected');
if (y === undefined) {
this.translate(x.x, x.y);
} else {
var delta = new Vec2(x, y);
for (var i = 0; i < this.paths.length; ++i)
this.paths[i].translateAbs(x, y);
for (var j = 0; j < this.boxes.length; ++j)
this.boxes[j] = this.boxes[j].translate(delta);
if (this.boundingBox !== null)
this.boundingBox = this.boundingBox.translate(delta);
}
};
module.exports = Visel;

View File

@ -0,0 +1,33 @@
/****************************************************************************
* Copyright 2017 EPAM Systems
*
* 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.
***************************************************************************/
function tfx(v) {
return (v - 0).toFixed(8);
}
function relBox(box) {
return {
x: box.x,
y: box.y,
width: box.width,
height: box.height
};
}
module.exports = {
tfx: tfx,
relBox: relBox
};