forked from enviPath/enviPy
Current Dev State
This commit is contained in:
295
static/js/ketcher2/script/render/draw.js
Normal file
295
static/js/ketcher2/script/render/draw.js
Normal 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
|
||||
};
|
||||
210
static/js/ketcher2/script/render/index.js
Normal file
210
static/js/ketcher2/script/render/index.js
Normal 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;
|
||||
96
static/js/ketcher2/script/render/options.js
Normal file
96
static/js/ketcher2/script/render/options.js
Normal 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;
|
||||
642
static/js/ketcher2/script/render/restruct/index.js
Normal file
642
static/js/ketcher2/script/render/restruct/index.js
Normal 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
|
||||
});
|
||||
647
static/js/ketcher2/script/render/restruct/reatom.js
Normal file
647
static/js/ketcher2/script/render/restruct/reatom.js
Normal 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;
|
||||
587
static/js/ketcher2/script/render/restruct/rebond.js
Normal file
587
static/js/ketcher2/script/render/restruct/rebond.js
Normal 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;
|
||||
63
static/js/ketcher2/script/render/restruct/rechiralflag.js
Normal file
63
static/js/ketcher2/script/render/restruct/rechiralflag.js
Normal 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;
|
||||
@ -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;
|
||||
112
static/js/ketcher2/script/render/restruct/refrag.js
Normal file
112
static/js/ketcher2/script/render/restruct/refrag.js
Normal 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;
|
||||
124
static/js/ketcher2/script/render/restruct/reloop.js
Normal file
124
static/js/ketcher2/script/render/restruct/reloop.js
Normal 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;
|
||||
75
static/js/ketcher2/script/render/restruct/reobject.js
Normal file
75
static/js/ketcher2/script/render/restruct/reobject.js
Normal 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;
|
||||
198
static/js/ketcher2/script/render/restruct/rergroup.js
Normal file
198
static/js/ketcher2/script/render/restruct/rergroup.js
Normal 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;
|
||||
63
static/js/ketcher2/script/render/restruct/rerxnarrow.js
Normal file
63
static/js/ketcher2/script/render/restruct/rerxnarrow.js
Normal 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;
|
||||
61
static/js/ketcher2/script/render/restruct/rerxnplus.js
Normal file
61
static/js/ketcher2/script/render/restruct/rerxnplus.js
Normal 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;
|
||||
378
static/js/ketcher2/script/render/restruct/resgroup.js
Normal file
378
static/js/ketcher2/script/render/restruct/resgroup.js
Normal 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;
|
||||
62
static/js/ketcher2/script/render/restruct/visel.js
Normal file
62
static/js/ketcher2/script/render/restruct/visel.js
Normal 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;
|
||||
33
static/js/ketcher2/script/render/util.js
Normal file
33
static/js/ketcher2/script/render/util.js
Normal 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
|
||||
};
|
||||
Reference in New Issue
Block a user