Files
enviPy-bayer/static/js/ketcher2/script/render/restruct/resgroup.js
2025-06-23 20:13:54 +02:00

379 lines
12 KiB
JavaScript

/****************************************************************************
* 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;