partridgejiang / Kekule.js

A Javascript cheminformatics toolkit.
http://partridgejiang.github.io/Kekule.js
MIT License
248 stars 61 forks source link

Creating an Electron Pushing Arrow between 2 molecules #261

Closed Luckp1089 closed 2 years ago

Luckp1089 commented 2 years ago

I am trying to create a reaction and using the chem viewer, I want to be able to display the electron arrow pushing from a bond to a node:

const arrow = new Kekule.Glyph.ElectronPushingArrow();
    arrow.setDonor(Bond);
    arrow.setReceptor(node);
    chemObj.appendChild(arrow);

However, I am not sure if I am creating the electron pushing arrow correctly or defining the Donor/Receptor correctly. I was able to create 2 molecules and pass that to the viewer, but I am having trouble creating the arrow between one bond on Molecule A to a node on Molecule B. Any assistance would be greatly appreciated!

Thanks!!

partridgejiang commented 2 years ago

Hi @Luckp1089, the following code may be used to add a electron pushing arrow manually:

const chemSpace = viewer.getChemObj();  // suppose now a ChemSpace with molecules are loaded in the viewer
// const chemSpace = composer.getChemObj();  // or retreive the ChemSpace instance from editor
const arrow = new Kekule.Glyph.ElectronPushingArrow();
arrow.setElectronCount(2);
// Temporarily clear the editing flag before adding arrow to chemSpace, especially when the chemSpace is in a editor.
// Otherwise the type of arrow may be automatically cast to other type of arrow if it is not connected with bond or atom
//chemSpace.setIsEditing(false);  
// add arrow to chemSpace, and the bond/atom are also in this chemSpace
chemSpace.appendChild(arrow);
// set the donor and receptor of arrow
arrow.setDonor(bond).setReceptor(atom);
// manually set the outlook style of arrow path
const path = arrow.getConnectorAt(0);
path.getPathParams().endArrowWidth = 0.25;
path.getPathParams().endArrowLength= 0.25;
// the curve of arrow is controlled by the control point of path
path.getControlPointAt(0).setCoord2D({'x': 1, 'y': 1});
// finally turn on the editing flag if the chemSpace is in an editor rather than viewer
//chemSpace.setIsEditing(true);

Note that the arrow, bond and atom should be in the same ChemSpace object to build up the electron pushing arrow.

Luckp1089 commented 2 years ago

Thanks for your response! I tried implementing what you had written and it doesn't render in my viewer. Below is the code snippet I am trying to get working:

import { Kekule } from 'kekule';

const structureUtils = Kekule.Editor.StructureUtils;
const defbondAngle = Math.PI * 2 / 3;

const mol1 = Kekule.IO.loadFormatData(molData, 'cml');
const chemViewer = new Kekule.ChemWidget.Viewer(document.getElementById('composer-step-1'));
const chemSpace = new Kekule.Molecule();
chemViewer.setChemObj(chemSpace);
const { nodes } = mol1;
const coords = nodes[nodes.length - 1].absCoord2D;
const mol2 = new Kekule.Molecule();
const hydrogenAtom = new Kekule.Atom().setSymbol('H').setCoord2D({ x: coords.x + 1, y: coords.y });
const newBond = new Kekule.Bond('bond1-step');
const coordsBr = structureUtils.calcPreferred2DBondGrowingLocation(hydrogenAtom, 0.8, defbondAngle, true);
const BrAtom = new Kekule.Atom().setSymbol('Br').setCoord2D(coordsBr);
newBond.setBondType(Kekule.BondType.COVALENT).setBondOrder(1).setConnectedObjs([hydrogenAtom, BrAtom]);
const arrow = new Kekule.Glyph.ElectronPushingArrow();
arrow.setElectronCount(2);
arrow.setDonor(newBond).setReceptor(nodes[nodes.length - 1]);

mol2.appendConnector(newBond);
mol2.appendNode(BrAtom);
mol2.appendNode(hydrogenAtom);
chemSpace.appendChild(mol1);
chemSpace.appendChild(mol2);
chemSpace.appendChild(arrow);

const path = arrow.getConnectorAt(0);
path.getPathParams().endArrowWidth = 0.25;
path.getPathParams().endArrowLength = 0.25;
path.getControlPointAt(0).setCoord2D({ x: 1, y: 1 });

And here is what it looks like (no arrow shows up):

image

partridgejiang commented 2 years ago

In the line3, the chemSpace should be an instance of ChemSpace rather than Molecule:

const chemSpace = new Kekule.ChemSpace();

And after creating the arrow, calling to the repaint method of viewer is essential, since the viewer widget will not listen to the change event of chem object inside and update the painting automatically (different from the editor widget):

viewer.repaint();

Or move the code chemViewer.setChemObj(chemSpace) to the end of the whole code snippet. Please have a try with those changes, :).

Luckp1089 commented 2 years ago

This worked perfectly, thank you so much!