in packages/ketcher-core/src/domain/serializers/mol/utils.js [138:322]
function rxnMerge(
mols,
nReactants,
nProducts,
nAgents,
shouldReactionRelayout,
) /* Struct */ {
// eslint-disable-line max-statements
/* reader */
const ret = new Struct();
const bbReact = [];
const bbAgent = [];
const bbProd = [];
const molReact = [];
const molAgent = [];
const molProd = [];
let j;
const bondLengthData = { cnt: 0, totalLength: 0 };
for (j = 0; j < mols.length; ++j) {
var mol = mols[j];
const bondLengthDataMol = mol.getBondLengthData();
bondLengthData.cnt += bondLengthDataMol.cnt;
bondLengthData.totalLength += bondLengthDataMol.totalLength;
}
if (SHOULD_RESCALE_MOLECULES) {
const avgBondLength =
1 /
(bondLengthData.cnt === 0
? 1
: bondLengthData.totalLength / bondLengthData.cnt);
for (j = 0; j < mols.length; ++j) {
mol = mols[j];
mol.scale(avgBondLength);
}
}
for (j = 0; j < mols.length; ++j) {
mol = mols[j];
const bb = mol.getCoordBoundingBoxObj();
if (!bb) continue; // eslint-disable-line no-continue
var fragmentType =
j < nReactants
? FRAGMENT.REACTANT // eslint-disable-line no-nested-ternary
: j < nReactants + nProducts
? FRAGMENT.PRODUCT
: FRAGMENT.AGENT;
if (fragmentType === FRAGMENT.REACTANT) {
bbReact.push(bb);
molReact.push(mol);
} else if (fragmentType === FRAGMENT.AGENT) {
bbAgent.push(bb);
molAgent.push(mol);
} else if (fragmentType === FRAGMENT.PRODUCT) {
bbProd.push(bb);
molProd.push(mol);
}
mol.atoms.forEach((atom) => {
atom.rxnFragmentType = fragmentType;
});
}
function shiftMol(ret, mol, bb, xorig, over) {
// eslint-disable-line max-params
const d = new Vec2(
xorig - bb.min.x,
over ? 1 - bb.min.y : -(bb.min.y + bb.max.y) / 2,
);
mol.atoms.forEach((atom) => {
atom.pp.add_(d); // eslint-disable-line no-underscore-dangle
});
mol.sgroups.forEach((item) => {
if (item.pp) item.pp.add_(d); // eslint-disable-line no-underscore-dangle
});
bb.min.add_(d); // eslint-disable-line no-underscore-dangle
bb.max.add_(d); // eslint-disable-line no-underscore-dangle
mol.mergeInto(ret);
return bb.max.x - bb.min.x;
}
if (shouldReactionRelayout) {
// reaction fragment layout
let xorig = 0;
for (j = 0; j < molReact.length; ++j) {
xorig += shiftMol(ret, molReact[j], bbReact[j], xorig, false) + 2.0;
}
xorig += 2.0;
for (j = 0; j < molAgent.length; ++j) {
xorig += shiftMol(ret, molAgent[j], bbAgent[j], xorig, true) + 2.0;
}
xorig += 2.0;
for (j = 0; j < molProd.length; ++j) {
xorig += shiftMol(ret, molProd[j], bbProd[j], xorig, false) + 2.0;
}
} else {
for (j = 0; j < molReact.length; ++j) molReact[j].mergeInto(ret);
for (j = 0; j < molAgent.length; ++j) molAgent[j].mergeInto(ret);
for (j = 0; j < molProd.length; ++j) molProd[j].mergeInto(ret);
}
let bb1;
let bb2;
let x;
let y;
let bbReactAll = null;
let bbProdAll = null;
for (j = 0; j < bbReact.length - 1; ++j) {
bb1 = bbReact[j];
bb2 = bbReact[j + 1];
x = (bb1.max.x + bb2.min.x) / 2;
y = (bb1.max.y + bb1.min.y + bb2.max.y + bb2.min.y) / 4;
ret.rxnPluses.add(new RxnPlus({ pp: new Vec2(x, y) }));
}
for (j = 0; j < bbReact.length; ++j) {
if (j === 0) {
bbReactAll = {};
bbReactAll.max = new Vec2(bbReact[j].max);
bbReactAll.min = new Vec2(bbReact[j].min);
} else {
bbReactAll.max = Vec2.max(bbReactAll.max, bbReact[j].max);
bbReactAll.min = Vec2.min(bbReactAll.min, bbReact[j].min);
}
}
for (j = 0; j < bbProd.length - 1; ++j) {
bb1 = bbProd[j];
bb2 = bbProd[j + 1];
x = (bb1.max.x + bb2.min.x) / 2;
y = (bb1.max.y + bb1.min.y + bb2.max.y + bb2.min.y) / 4;
ret.rxnPluses.add(new RxnPlus({ pp: new Vec2(x, y) }));
}
for (j = 0; j < bbProd.length; ++j) {
if (j === 0) {
bbProdAll = {};
bbProdAll.max = new Vec2(bbProd[j].max);
bbProdAll.min = new Vec2(bbProd[j].min);
} else {
bbProdAll.max = Vec2.max(bbProdAll.max, bbProd[j].max);
bbProdAll.min = Vec2.min(bbProdAll.min, bbProd[j].min);
}
}
bb1 = bbReactAll;
bb2 = bbProdAll;
const defaultArrowLength = 2;
if (!bb1 && !bb2) {
ret.rxnArrows.add(
new RxnArrow({
mode: 'open-angle',
pos: [new Vec2(0, 0), new Vec2(defaultArrowLength, 0)],
}),
);
} else {
let v1 = bb1 ? new Vec2(bb1.max.x, (bb1.max.y + bb1.min.y) / 2) : null;
let v2 = bb2 ? new Vec2(bb2.min.x, (bb2.max.y + bb2.min.y) / 2) : null;
const defaultOffset = 3;
if (!v1) v1 = new Vec2(v2.x - defaultOffset, v2.y);
if (!v2) v2 = new Vec2(v1.x + defaultOffset, v1.y);
const arrowCenter = Vec2.lc2(v1, 0.5, v2, 0.5);
const arrowStart = new Vec2(
arrowCenter.x - 0.5 * defaultArrowLength,
arrowCenter.y,
arrowCenter.z,
);
const arrowEnd = new Vec2(
arrowCenter.x + 0.5 * defaultArrowLength,
arrowCenter.y,
arrowCenter.z,
);
ret.rxnArrows.add(
new RxnArrow({
mode: 'open-angle',
pos: [arrowStart, arrowEnd],
}),
);
}
ret.isReaction = true;
return ret;
}