//@ts-check
const Drawer = require('./Drawer')
const Parser = require('./Parser')
const ReactionParser = require('./ReactionParser')
const SvgDrawer = require('./SvgDrawer')
const ReactionDrawer = require('./ReactionDrawer')
const SvgWrapper = require('./SvgWrapper')
const Options = require('./Options');
class SmilesDrawer {
constructor(moleculeOptions = {}, reactionOptions = {}) {
this.drawer = new SvgDrawer(moleculeOptions);
// moleculeOptions gets edited in reactionOptions, so clone
this.reactionDrawer = new ReactionDrawer(reactionOptions, JSON.parse(JSON.stringify(this.drawer.opts)));
}
static apply(moleculeOptions = {}, reactionOptions = {}, attribute = 'data-smiles', theme = 'light', successCallback = null, errorCallback = null) {
const drawer = new SmilesDrawer(moleculeOptions, reactionOptions);
drawer.apply(attribute, theme, successCallback, errorCallback);
}
apply(attribute = 'data-smiles', theme = 'light', successCallback = null, errorCallback = null) {
let elements = document.querySelectorAll(`[${attribute}]`);
elements.forEach(element => {
let smiles = element.getAttribute(attribute);
if (smiles === null) {
throw Error('No SMILES provided.');
}
let currentTheme = theme;
let weights = null;
if (element.hasAttribute('data-smiles-theme')) {
currentTheme = element.getAttribute('data-smiles-theme');
}
if (element.hasAttribute('data-smiles-weights')) {
weights = element.getAttribute('data-smiles-weights').split(",").map(parseFloat);
}
if (element.hasAttribute('data-smiles-reactant-weights') ||
element.hasAttribute('data-smiles-reagent-weights') ||
element.hasAttribute('data-smiles-product-weights')) {
weights = { reactants: [], reagents: [], products: [] };
if (element.hasAttribute('data-smiles-reactant-weights')) {
weights.reactants = element.getAttribute('data-smiles-reactant-weights').split(';').map(v => {
return v.split(',').map(parseFloat)
});
}
if (element.hasAttribute('data-smiles-reagent-weights')) {
weights.reagents = element.getAttribute('data-smiles-reagent-weights').split(';').map(v => {
return v.split(',').map(parseFloat)
});
}
if (element.hasAttribute('data-smiles-product-weights')) {
weights.products = element.getAttribute('data-smiles-product-weights').split(';').map(v => {
return v.split(',').map(parseFloat)
});
}
}
if (element.hasAttribute('data-smiles-options') || element.hasAttribute('data-smiles-reaction-options')) {
let moleculeOptions = {};
if (element.hasAttribute('data-smiles-options')) {
moleculeOptions = JSON.parse(element.getAttribute('data-smiles-options').replaceAll('\'', '"'));
}
let reactionOptions = {};
if (element.hasAttribute('data-smiles-reaction-options')) {
reactionOptions = JSON.parse(element.getAttribute('data-smiles-reaction-options').replaceAll('\'', '"'));
}
let smilesDrawer = new SmilesDrawer(moleculeOptions, reactionOptions);
smilesDrawer.draw(smiles, element, currentTheme, successCallback, errorCallback, weights);
} else {
this.draw(smiles, element, currentTheme, successCallback, errorCallback, weights);
}
});
}
/**
* Draw the smiles to the target.
* @param {String} smiles The SMILES to be depicted.
* @param {*} target The target element.
* @param {String} theme The theme.
* @param {?CallableFunction} successCallback The function called on success.
* @param {?CallableFunction} errorCallback The function called on error.
* @param {?Number[]|Object} weights The weights for the gaussians.
*/
draw(smiles, target, theme = 'light', successCallback = null, errorCallback = null, weights = null) {
// get the settings
let rest = [];
[smiles, ...rest] = smiles.split(' ');
let info = rest.join(' ');
let settings = {};
if (info.includes('__')) {
let settingsString = info.substring(
info.indexOf('__') + 2,
info.lastIndexOf('__')
);
settings = JSON.parse(settingsString.replaceAll('\'', '"'));
}
let defaultSettings = {
textAboveArrow: '{reagents}',
textBelowArrow: ''
}
settings = Options.extend(true, defaultSettings, settings);
if (smiles.includes('>')) {
try {
this.drawReaction(smiles, target, theme, settings, weights, successCallback);
} catch (err) {
if (errorCallback) {
errorCallback(err);
} else {
console.error(err);
}
}
} else {
try {
this.drawMolecule(smiles, target, theme, weights, successCallback);
} catch (err) {
if (errorCallback) {
errorCallback(err);
} else {
console.error(err);
}
}
}
}
drawMolecule(smiles, target, theme, weights, callback) {
let parseTree = Parser.parse(smiles);
if (target === null || target === 'svg') {
let svg = this.drawer.draw(parseTree, null, theme, weights);
let dims = this.getDimensions(svg);
svg.setAttributeNS(null, 'width', '' + dims.w);
svg.setAttributeNS(null, 'height', '' + dims.h);
if (callback) {
callback(svg);
}
} else if (target === 'canvas') {
let canvas = this.svgToCanvas(this.drawer.draw(parseTree, null, theme, weights));
if (callback) {
callback(canvas);
}
} else if (target === 'img') {
let img = this.svgToImg(this.drawer.draw(parseTree, null, theme, weights));
if (callback) {
callback(img);
}
} else if (target instanceof HTMLImageElement) {
this.svgToImg(this.drawer.draw(parseTree, null, theme, weights), target);
if (callback) {
callback(target);
}
} else if (target instanceof SVGElement) {
this.drawer.draw(parseTree, target, theme, weights);
if (callback) {
callback(target);
}
} else {
let elements = document.querySelectorAll(target);
elements.forEach(element => {
let tag = element.nodeName.toLowerCase();
if (tag === 'svg') {
this.drawer.draw(parseTree, element, theme, weights);
// let dims = this.getDimensions(element);
// element.setAttributeNS(null, 'width', '' + dims.w);
// element.setAttributeNS(null, 'height', '' + dims.h);
if (callback) {
callback(element);
}
} else if (tag === 'canvas') {
this.svgToCanvas(this.drawer.draw(parseTree, null, theme, weights), element);
if (callback) {
callback(element);
}
} else if (tag === 'img') {
this.svgToImg(this.drawer.draw(parseTree, null, theme, weights), element);
if (callback) {
callback(element);
}
}
});
}
}
drawReaction(smiles, target, theme, settings, weights, callback) {
let reaction = ReactionParser.parse(smiles);
if (target === null || target === 'svg') {
let svg = this.reactionDrawer.draw(reaction, null, theme);
let dims = this.getDimensions(svg);
svg.setAttributeNS(null, 'width', '' + dims.w);
svg.setAttributeNS(null, 'height', '' + dims.h);
if (callback) {
callback(svg);
}
} else if (target === 'canvas') {
let canvas = this.svgToCanvas(this.reactionDrawer.draw(reaction, null, theme, weights, settings.textAboveArrow, settings.textBelowArrow));
if (callback) {
callback(canvas);
}
} else if (target === 'img') {
let img = this.svgToImg(this.reactionDrawer.draw(reaction, null, theme, weights, settings.textAboveArrow, settings.textBelowArrow));
if (callback) {
callback(img);
}
} else if (target instanceof HTMLImageElement) {
this.svgToImg(this.reactionDrawer.draw(reaction, null, theme, weights, settings.textAboveArrow, settings.textBelowArrow), target);
if (callback) {
callback(target);
}
} else if (target instanceof SVGElement) {
this.reactionDrawer.draw(reaction, target, theme, weights, settings.textAboveArrow, settings.textBelowArrow);
if (callback) {
callback(target);
}
} else {
let elements = document.querySelectorAll(target);
elements.forEach(element => {
let tag = element.nodeName.toLowerCase();
if (tag === 'svg') {
this.reactionDrawer.draw(reaction, element, theme, weights, settings.textAboveArrow, settings.textBelowArrow);
// The svg has to have a css width and height set for the other
// tags, however, here it would overwrite the chosen width and height
if (this.reactionDrawer.opts.scale <= 0) {
element.style.width = null;
element.style.height = null;
}
// let dims = this.getDimensions(element);
// element.setAttributeNS(null, 'width', '' + dims.w);
// element.setAttributeNS(null, 'height', '' + dims.h);
if (callback) {
callback(element);
}
} else if (tag === 'canvas') {
this.svgToCanvas(this.reactionDrawer.draw(reaction, null, theme, weights, settings.textAboveArrow, settings.textBelowArrow), element);
if (callback) {
callback(element);
}
} else if (tag === 'img') {
this.svgToImg(this.reactionDrawer.draw(reaction, null, theme, weights, settings.textAboveArrow, settings.textBelowArrow), element);
if (callback) {
callback(element);
}
}
});
}
}
svgToCanvas(svg, canvas = null) {
if (canvas === null) {
canvas = document.createElement('canvas');
}
let dims = this.getDimensions(canvas, svg);
SvgWrapper.svgToCanvas(svg, canvas, dims.w, dims.h);
return canvas;
}
svgToImg(svg, img = null) {
if (img === null) {
img = document.createElement('img');
}
let dims = this.getDimensions(img, svg);
SvgWrapper.svgToImg(svg, img, dims.w, dims.h);
return img;
}
/**
*
* @param {HTMLImageElement|HTMLCanvasElement|SVGElement} element
* @param {SVGElement} svg
* @returns {{w: Number, h: Number}} The width and height.
*/
getDimensions(element, svg = null) {
let w = this.drawer.opts.width;
let h = this.drawer.opts.height;
if (this.drawer.opts.scale <= 0) {
if (w === null) {
w = element.width;
}
if (h === null) {
h = element.height;
}
if (element.style.width !== "") {
w = parseInt(element.style.width);
}
if (element.style.height !== "") {
h = parseInt(element.style.height);
}
} else if (svg) {
w = parseFloat(svg.style.width);
h = parseFloat(svg.style.height);
}
return { w: w, h: h };
}
}
module.exports = SmilesDrawer;