forked from enviPath/enviPy
648 lines
21 KiB
JavaScript
648 lines
21 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 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;
|