kekule
Version:
Open source JavaScript toolkit for chemoinformatics
599 lines (581 loc) • 17.7 kB
JavaScript
/**
* @fileoverview
* Glyphs of some chem symbols.
* @author Partridge Jiang
*/
/*
* requires /lan/classes.js
* requires /core/kekule.common.js
* requires /core/kekule.structures.js
* requires /chemdoc/kekule.glyph.base.js
* requires /chemdoc/kekule.glyph.pathGlyphs.js
* requires /chemdoc/kekule.glyph.lines.js
*/
(function(){
"use strict";
var OU = Kekule.ObjUtils;
var NT = Kekule.Glyph.NodeType;
var PT = Kekule.Glyph.PathType;
var EAU = Kekule.Glyph.ElectronArrowGlyphUtils;
/**
* Heat symbol (a triangle) of reaction equation.
* @class
* @augments Kekule.Glyph.Polygon
*/
Kekule.Glyph.HeatSymbol = Class.create(Kekule.Glyph.Polygon,
/** @lends Kekule.Glyph.HeatSymbol# */
{
/** @private */
CLASS_NAME: 'Kekule.Glyph.HeatSymbol',
/** @constructs */
initialize: function(/*$super, */id, refLength, initialParams, coord2D, coord3D)
{
this.tryApplySuper('initialize', [id, refLength, initialParams, coord2D, coord3D]) /* $super(id, refLength, initialParams, coord2D, coord3D) */;
if (this.setRenderOption) // avoid error if render module is not loaded
this.setRenderOption('strokeWidth', 1.5);
},
/** @ignore */
getRefLengthRatio: function()
{
return 0.25;
},
/** @private */
doCreateDefaultStructure: function(/*$super, */refLength, initialParams)
{
initialParams.edgeCount = 3;
initialParams.nodeProps = Object.extend(initialParams.nodeProps || {}, {'interactMode': Kekule.ChemObjInteractMode.HIDDEN});
initialParams.connectorProps = Object.extend(initialParams.connectorProps || {}, {'interactMode': Kekule.ChemObjInteractMode.HIDDEN});
return this.tryApplySuper('doCreateDefaultStructure', [refLength, initialParams]) /* $super(refLength, initialParams) */;
},
/** @private */
_applyParamsToConnector: function(/*$super, */connector, initialParams)
{
return this.tryApplySuper('_applyParamsToConnector', [connector, initialParams]) /* $super(connector, initialParams) */;
}
});
/**
* A glyph "add" symbol in reaction.
* @class
* @augments Kekule.Glyph.PathGlyph
*/
Kekule.Glyph.AddSymbol = Class.create(Kekule.Glyph.PathGlyph,
/** @lends Kekule.Glyph.AddSymbol# */
{
/** @private */
CLASS_NAME: 'Kekule.Glyph.AddSymbol',
/** @constructs */
initialize: function(/*$super, */id, refLength, initialParams, coord2D, coord3D)
{
this.tryApplySuper('initialize', [id, refLength, initialParams, coord2D, coord3D]) /* $super(id, refLength, initialParams, coord2D, coord3D) */;
if (this.setRenderOption) // avoid error if render module is not loaded
this.setRenderOption('strokeWidth', 1.5);
},
/** @private */
getRefLengthRatio: function()
{
return 0.5;
},
/** @private */
doCreateDefaultStructure: function(refLength, initialParams)
{
var nodeProps = {'interactMode': Kekule.ChemObjInteractMode.HIDDEN};
var connectorProps = {'interactMode': Kekule.ChemObjInteractMode.HIDDEN};
// initialParams can include additional field: lineLength
var C = Kekule.CoordUtils;
var coord2D = {'x': 0, 'y': 0};
var coord3D = {'x': 0, 'y': 0, 'z': 0};
var length = refLength * this.getRefLengthRatio() * (initialParams.lineLength || 1) / 2;
var baseNode = new Kekule.Glyph.PathGlyphNode(null, null, coord2D, coord3D);
baseNode.setPropValues(nodeProps);
this.appendNode(baseNode);
var adjustDeltas = [
{x: -length, y: 0},
{x: 0, y: -length},
{x: length, y: 0},
{x: 0, y: length}
];
for (var i = 0, l = adjustDeltas.length; i < l; ++i)
{
var delta = adjustDeltas[i];
var node = new Kekule.Glyph.PathGlyphNode(null, null, C.add(coord2D, delta), C.add(coord3D, delta));
node.setPropValues(nodeProps);
var connector = new Kekule.Glyph.PathGlyphConnector(null, PT.LINE, [baseNode, node]);
connector.setPropValues(connectorProps);
this._applyParamsToConnector(connector, initialParams);
this.appendNode(node);
this.appendConnector(connector);
}
},
/** @private */
_applyParamsToConnector: function(connector, initialParams)
{
connector.setPathParams(initialParams);
}
});
/////////////// Set of line based glyphs ////////////////////
/**
* A simple line segment glyph.
* @class
* @augments Kekule.Glyph.StraightLine
*/
Kekule.Glyph.Segment = Class.create(Kekule.Glyph.StraightLine,
/** @lends Kekule.Glyph.Segment# */
{
/** @private */
CLASS_NAME: 'Kekule.Glyph.Segment'
});
/**
* Enumeration of reaction arrow types.
* @enum
*/
Kekule.Glyph.ReactionArrowType = {
NORMAL: 'normal',
REVERSIBLE: 'reversible',
RESONANCE: 'resonance',
RETROSYNTHESIS: 'retrosynthesis',
CUSTOM: 'custom'
};
Kekule.Glyph.ReactionArrowType.getDefPathParamOfArrowType = function(arrowType)
{
var RAT = Kekule.Glyph.ReactionArrowType;
var AT = Kekule.Glyph.ArrowType;
var AS = Kekule.Glyph.ArrowSide;
var result;
switch (arrowType)
{
case RAT.NORMAL:
result = {
'startArrowType': AT.NONE,
'endArrowType': AT.OPEN,
'endArrowSide': AS.BOTH,
'lineCount': 1
};
break;
case RAT.REVERSIBLE:
result = {
'startArrowType': AT.OPEN,
'startArrowSide': AS.REVERSED,
'endArrowType': AT.OPEN,
'endArrowSide': AS.SINGLE,
'lineGap': 0.1,
'lineCount': 2
};
break;
case RAT.RESONANCE:
result = {
'startArrowType': AT.OPEN,
'startArrowSide': AS.BOTH,
'endArrowType': AT.OPEN,
'endArrowSide': AS.BOTH,
'lineCount': 1
};
break;
case RAT.RETROSYNTHESIS:
result = {
'startArrowType': AT.NONE,
'endArrowType': AT.OPEN,
'endArrowSide': AS.BOTH,
'lineGap': 0.1,
'lineCount': 2
};
break;
default: // other situation is not a reaction arrow
result = null;
}
return result;
};
/**
* A simple line segment glyph.
* @class
* @augments Kekule.Glyph.StraightLine
*/
Kekule.Glyph.ReactionArrow = Class.create(Kekule.Glyph.StraightLine,
/** @lends Kekule.Glyph.ReactionArrow# */
{
/** @private */
CLASS_NAME: 'Kekule.Glyph.ReactionArrow',
/** @private */
initProperties: function() {
this.defineProp('reactionType', {
'dataType': DataType.STRING,
'enumSource': Kekule.Glyph.ReactionArrowType,
'setter': function(value) {
this.setPropStoreFieldValue('reactionType', value);
this.reactionTypeChanged(value);
}
});
},
/** @ignore */
initPropValues: function(/*$super*/)
{
this.tryApplySuper('initPropValues') /* $super() */;
this.setReactionType(Kekule.Glyph.ReactionArrowType.NORMAL);
},
/** @ignore */
doCreateDefaultStructure: function(/*$super, */refLength, initialParams)
{
var creationParams = initialParams;
var rType = initialParams.reactionType || this.getReactionType();
if (rType)
{
var defParams = this._getPathParamOfArrowType(rType);
creationParams = Object.extend(defParams, initialParams);
}
var result = this.tryApplySuper('doCreateDefaultStructure', [refLength, creationParams]) /* $super(refLength, creationParams) */;
if (rType)
this.setReactionType(rType);
return result;
},
/** @private */
reactionTypeChanged: function(newArrowType)
{
var pParams = this._getPathParamOfArrowType(newArrowType);
if (pParams)
{
// update path params
var connector = this.getConnectorAt(0);
if (connector)
connector.modifyPathParams(pParams);
}
},
/** @private */
_getPathParamOfArrowType: function(arrowType)
{
return Kekule.Glyph.ReactionArrowType.getDefPathParamOfArrowType(arrowType);
}
});
////////////// Set of arc based glyphs //////////////////////
/**
* Electron pushing arrow (usually connected with two bonds or bond/atom) in reaction.
* @class
* @augments Kekule.Glyph.BaseArc
*
* @property {Kekule.ChemStructureObject} Donor The electron donor node or connector.
* @property {Kekule.ChemStructureObject} Receptor The electron receptor node or connector.
* @property {Kekule.ChemStructureObject} DirectDonor The direct electron donor object (e.g., the lone pair) of donor node/connector.
* @property {Kekule.ChemStructureObject} DirectReceptor The direct electron receptor object (e.g., the lone pair) of donor node/connector.
*/
Kekule.Glyph.ElectronPushingArrow = Class.create(Kekule.Glyph.BaseArc,
/** @lends Kekule.Glyph.ElectronPushingArrow# */
{
/** @private */
CLASS_NAME: 'Kekule.Glyph.ElectronPushingArrow',
/** @private */
initProperties: function()
{
this.defineProp('electronCount', {
'dataType': DataType.INT,
'getter': function() {
var arrowSide = this._getValidArrowSide();
var ASide = Kekule.Glyph.ArrowSide;
if (arrowSide === ASide.BOTH)
return 2;
else if ([ASide.SINGLE, ASide.REVERSED].indexOf(arrowSide) >= 0)
return 1;
else
return null;
},
'setter': function(value) {
var ASide = Kekule.Glyph.ArrowSide;
var arrowPos = this._getValidArrowPos();
var conn = this.getConnectorAt(0);
if (!arrowPos)
{
conn.getPathParams().endArrowType = Kekule.Glyph.ArrowType.OPEN;
arrowPos = 'end';
}
var params = conn.getPathParams();
if (value >= 2)
{
params[arrowPos + 'ArrowSide'] = ASide.BOTH;
}
else if (value === 1)
{
params[arrowPos + 'ArrowSide'] = ASide.SINGLE;
}
conn.setPathParams(params);
}
});
this.defineProp('directReceptor', {
'dataType': 'Kekule.ChemStructureObject',
'getter': function() {
var node = this.getNodeAt(1);
return this._getValidElectronTarget(node);
},
'setter': function(value) {
var node = this.getNodeAt(1);
this._setValidElectronTarget(node, value);
}
});
this.defineProp('directDonor', {
'dataType': 'Kekule.ChemStructureObject',
'getter': function() {
var node = this.getNodeAt(0);
return this._getValidElectronTarget(node);
},
'setter': function(value) {
var node = this.getNodeAt(0);
this._setValidElectronTarget(node, value);
}
});
this.defineProp('receptor', {
'dataType': 'Kekule.ChemStructureObject',
'getter': function() {
var node = this.getNodeAt(1);
return this._getValidElectronTargetNodeOrConnector(node);
},
'setter': function(value) {
var node = this.getNodeAt(1);
this._setValidElectronTarget(node, value);
}
});
this.defineProp('donor', {
'dataType': 'Kekule.ChemStructureObject',
'getter': function() {
var node = this.getNodeAt(0);
return this._getValidElectronTargetNodeOrConnector(node);
},
'setter': function(value) {
var node = this.getNodeAt(0);
this._setValidElectronTarget(node, value);
}
});
},
/** @ignore */
doCreateDefaultStructure: function(/*$super, */refLength, initialParams)
{
var result = this.tryApplySuper('doCreateDefaultStructure', [refLength, initialParams]) /* $super(refLength, initialParams) */;
if (initialParams.electronCount)
this.setElectronCount(initialParams.electronCount);
return result;
},
/** @private */
_isValidElectronTarget: function(obj)
{
/*
var result = (obj instanceof Kekule.ChemStructureNode) || (obj instanceof Kekule.ChemStructureConnector);
if (!result)
{
var parent = obj.getParent && obj.getParent();
if (parent) // the marker of node/connector (e.g., electron pair) can also be a valid target
result = (parent instanceof Kekule.ChemStructureNode) || (parent instanceof Kekule.ChemStructureConnector);
}
return result;
*/
return this._isValidChemNodeOrConnectorStickTarget(obj);
},
/** @private */
_getValidElectronTarget: function(glyphNode)
{
/*
var stickTarget = glyphNode && glyphNode.getCoordStickTarget && glyphNode.getCoordStickTarget();
if (stickTarget && this._isValidElectronTarget(stickTarget))
return stickTarget;
else
return null;
*/
return Kekule.Glyph.ElectronArrowGlyphUtils.getValidElectronTarget(glyphNode);
},
/** @private */
_setValidElectronTarget: function(glyphNode, target)
{
/*
if (glyphNode.getAllowCoordStickTo && glyphNode.getAllowCoordStickTo(target))
{
if (!target)
glyphNode.setCoordStickTarget(null);
else if (this._isValidElectronTarget(target))
glyphNode.setCoordStickTarget(target);
}
*/
Kekule.Glyph.ElectronArrowGlyphUtils.setValidElectronTarget(glyphNode, target);
return this;
},
/** @private */
_getValidElectronTargetNodeOrConnector: function(glyphNode)
{
/*
var result = null;
var target = this._getValidElectronTarget(glyphNode);
if (target)
{
if (target instanceof Kekule.ChemMarker.BaseMarker)
result = target.getParent();
else
result = target;
}
return result;
*/
return Kekule.Glyph.ElectronArrowGlyphUtils.getValidElectronTargetNodeOrConnector(glyphNode);
},
/** @private */
_getValidArrowPos: function()
{
var conn = this.getConnectorAt(0);
var params = conn && conn.getPathParams();
if (params)
{
if (params.endArrowType)
return 'end';
else if (params.startArrowType)
return 'start';
}
return null;
},
/** @private */
_getValidArrowSide: function()
{
var conn = this.getConnectorAt(0);
var params = conn && conn.getPathParams();
if (params)
{
if (params.endArrowType)
return params.endArrowSide || Kekule.Glyph.ArrowSide.DEFAULT;
if (params.startArrowType)
return params.startArrowSide || Kekule.Glyph.ArrowSide.DEFAULT;
}
return null;
},
/** @ignore */
_applyParamsToConnector: function(connector, initialParams)
{
var p = Object.create(initialParams);
if (Kekule.ObjUtils.isUnset(initialParams.autoOffset))
p.autoOffset = true;
connector.setPathParams(p);
}
});
/**
* Bond forming electron pushing arrow in reaction.
* @class
* @augments Kekule.Glyph.BaseTwinArc
*
* @property {Array} Donors The two electron donor nodes to form the bond.
* @property {Array} DirectDonors The direct electron donor objects (e.g., the lone pair) of donor nodes.
*/
Kekule.Glyph.BondFormingElectronPushingArrow = Class.create(Kekule.Glyph.BaseTwinArc,
/** @lends Kekule.Glyph.BondFormingElectronPushingArrow# */
{
/** @private */
CLASS_NAME: 'Kekule.Glyph.BondFormingElectronPushingArrow',
/** @private */
initProperties: function()
{
this.defineProp('electronCount', {
'dataType': DataType.INT,
'getter': function()
{
var result = null;
for (var i = 0, l = this.getConnectorCount(); i < l; ++i)
{
var count = this._getConnectorElectronCount(this.getConnectorAt(i));
if (OU.notUnset(count))
{
if (OU.isUnset(result))
result = count;
else if (result !== count)
return null;
}
}
return result * this.getConnectorCount();
},
'setter': function(value) {
if (value === this.getElectronCount())
return;
var perValue = this.getConnectorCount()? value / this.getConnectorCount(): value;
perValue = Math.round(perValue);
if (perValue <= 0)
perValue = 1;
var ASide = Kekule.Glyph.ArrowSide;
//var arrowPos = this._getValidArrowPos();
var connectors = this.getConnectors();
var arrowPos;
for (var i = 0, l = connectors.length; i < l; ++i)
{
var conn = connectors[i];
//if (!arrowPos)
{
conn.getPathParams().endArrowType = Kekule.Glyph.ArrowType.OPEN;
arrowPos = 'end';
}
var params = conn.getPathParams();
if (perValue >= 2)
{
params[arrowPos + 'ArrowSide'] = ASide.BOTH;
}
else if (perValue === 1)
{
params[arrowPos + 'ArrowSide'] = (i % 2)? ASide.SINGLE: ASide.REVERSED;
}
conn.setPathParams(params);
}
}
});
this.defineProp('directDonors', {
'dataType': DataType.ARRAY,
'getter': function() {
var nodes = this._getArrowStartingNodes();
var result = [];
for (var i = 0, l = nodes.length; i < l; ++i)
{
var obj = EAU.getValidElectronTarget(nodes[i]);
if (obj)
result.push(obj);
}
return result;
},
'setter': function(value) {
var nodes = this._getArrowStartingNodes();
if (nodes.length)
{
for (var i = 0, l = nodes.length; i < l; ++i)
{
var node = nodes[i];
var obj = value[i];
EAU.setValidElectronTarget(node, value);
}
}
}
});
this.defineProp('donors', {
'dataType': DataType.ARRAY,
'getter': function() {
var nodes = this._getArrowStartingNodes();
var result = [];
for (var i = 0, l = nodes.length; i < l; ++i)
{
var obj = EAU.getValidElectronTargetNodeOrConnector(nodes[i]);
if (obj)
result.push(obj);
}
return result;
},
'setter': function(value) {
this.setDirectDonors(value);
}
});
},
/** @private */
_getConnectorElectronCount: function(connector)
{
var params = connector && connector.getPathParams();
if (params)
{
var ASide = Kekule.Glyph.ArrowSide;
var arrowSide = (params.endArrowType)? params.endArrowSide || Kekule.Glyph.ArrowSide.DEFAULT: null;
if (arrowSide === ASide.BOTH)
return 2;
else if ([ASide.SINGLE, ASide.REVERSED].indexOf(arrowSide) >= 0)
return 1;
}
return null;
},
/** @ignore */
doCreateDefaultStructure: function(/*$super, */refLength, initialParams)
{
var result = this.tryApplySuper('doCreateDefaultStructure', [refLength, initialParams]) /* $super(refLength, initialParams) */;
this.setElectronCount(initialParams.electronCount || 1);
return result;
},
});
})();