UNPKG

kekule

Version:

Open source JavaScript toolkit for chemoinformatics

1,475 lines (1,430 loc) 61.2 kB
/** * @fileoverview * Extend some core classes for displaying purpose. * @author Partridge Jiang */ /* * requires /lan/classes.js * requires /core/kekule.structures.js * requires /core/kekule.chemUtils.js * requires /data/kekule.dataUtils.js * requires /render/kekule.render.utils.js */ (function() { var K = Kekule; var NL = Kekule.ChemStructureNodeLabels; var CM = Kekule.CoordMode; //var C = Kekule.Render.getRenderConfigs(); /** @ignore */ ClassEx.extend(Kekule.ChemObject, /** @lends Kekule.ChemObject# */ { /** * Returns default coord position of this object. * Descendants may override this method * @param {Int} coordMode * @returns {Int} Value from {@link Kekule.Render.CoordPos} */ getDefCoordPos: function(coordMode) { return Kekule.Render.CoordPos.CENTER; }, /** * Returns the actual base coord position of this object. * @param {Int} coordMode * @returns {Int} Value from {@link Kekule.Render.CoordPos} */ getCoordPos: function(coordMode) { var result = (coordMode === Kekule.CoordMode.COORD3D)? this.getPropStoreFieldValue('coordPos3D'): this.getPropStoreFieldValue('coordPos2D'); if (Kekule.ObjUtils.isUnset(result)) // not explict set, use default value result = this.getDefCoordPos(coordMode); return result; }, /** * @property {Hash} renderOptions Options to render this chem object in 2D. * This property may has the following fields: * moleculeDisplayType: Value from {@link Kekule.Render.MoleculeDisplayType}. * If object is a structure fragment, this value will determinate the display type of molecule. * renderType: Value from {@link Kekule.Render.BondRenderType}. * If object is a connector, this value determinates its outlook shape. * nodeDisplayMode: Value from {@link Kekule.Render.NodeLabelDisplayMode} * If object is a node, this value will determinate its display mode, shown or hidden. * hydrogenDisplayLevel: Value from {@link Kekule.Render.HydrogenDisplayLevel}. * Not used yet. * showCharge: Boolean value. Determinte whether a node should show charge. * chargeMarkType: Int, value from {@link Kekule.Render.ChargeMarkRenderType}. * chargeMarkFontSize: Float. * chargeMarkMargin: Float. * chargeMarkCircleWidth: Float. * distinguishSingletAndTripletRadical: bool, * fontSize, fontFamily, supFontSizeRatio, subFontSizeRatio, * superscriptOverhang, subscriptOversink, textBoxXAlignment, textBoxYAlignment: * Those values can adjust the outlook of node labels. * customLabel: Custom label (string) to display for chem node. For instance, to a 18O- atom, * if customLabel is set to 'oxygen', then 18oxygen- will be displayed instead. * bondLineWidth, boldBondLineWidth, hashSpacing, multipleBondSpacingRatio, * multipleBondSpacingAbs, multipleBondMaxAbsSpacing, bondArrowLength, bondArrowWidth, * bondWedgeWidth, bondWedgeHashMinWidth: * Those values are for drawing connectors. * bondLengthScaleRatio: The actual bond length will be scaled according to this value. * color: String. Color for drawing node label or connector shape. * If this value is set, atomColor/bondColor/useAtomSpecifiedColor will be ignored. * // If atomColor or bondColor is set, this value is ignored. * atomColor: String. Color used for node / atom. * bondColor: String. Color used for connector / bond. * useAtomSpecifiedColor Whether use different color on different atoms. * atomRadius: Int, when an atom is draw without a label (e.g., C in skeletal mode), a cicle of atomRadius will be drawn. * 0 will draw nothing in atom's location. * expanded: Boolean. Whether a subgroup has been expanded to display all its children. * * strokeWidth, strokeColor: stroke property when drawing glyph. * fillColor: fill color when drawing glyph. * color: stroke and fill color to draw glyph. If strokeColor and fillColor is set, this * property is ignored. * * @property {Array} overrideRenderOptionItems: array of hash objects. Render options appointed by them has the highest priority * (even override the settings of object itself). * * @property {Hash} render3DOptions Options to render this chem object in 3D. * This property may has the following fields: * bondSpliceMode: Value from {@link Kekule.Render.Bond3DSpliceMode}. * displayMultipleBond: Boolean. Whether display double or triple bond in multi-line. * This property is used in whole structure. Individual bond can not has different settings. * useVdWRadius: Boolean. Whether use atom's von dar Waals radius to draw 3D model. * nodeRadius: Float. Explicitly set the node's radius. * connectorRadius: Float. Explicitly set the connector's radius. * nodeRadiusRatio: Float. * connectorRadiusRatio: Float. * connectorLineWidth: Float. * color: String. Color for drawing node or connector shape. * // If atomColor or bondColor is set, this value is ignored. * If this value is set, atomColor/bondColor/useAtomSpecifiedColor will be ignored. * atomColor: String. Color used for node / atom. * bondColor: String. Color used for connector / bond. * useAtomSpecifiedColor Whether use different color on different atoms. * hideHydrogens: Bool. Whether hide hydrogen atoms in 3D model. * * @property {Array} overrideRender3DOptionItems: array of hash objects. Render options appointed by them has the highest priority * (even override the settings of object itself). * * //@property {String} interactState State of object in interaction with user. * // Values from {@link Kekule.Render.InteractState}. */ /* initProperties: function($origin) { $origin(); // new property, decide the 2D render style of a object (node or connector) this.defineProp('renderOptions', {'dataType': DataType.OBJECT}); // new property, decide the 3D render style of a object (node or connector) this.defineProp('render3DOptions', {'dataType': DataType.OBJECT}); // new property, decide the interaction state of a object (node or connector) this.defineProp('interactState', {'dataType': DataType.STRING}); }, */ /** * Get total override render options appointed by overrideOptionItems. */ getOverrideRenderOptions: function(overrideItems) { if (overrideItems && overrideItems.length) { var result = {}; for (var i = 0, l = overrideItems.length; i < l; ++i) { result = Object.extend(result, overrideItems[i]); } return result; } return null; }, /** @private */ _appendOverrideRenderOptionItem: function(item, targetPropName) { var targetItemArray = this.getPropStoreFieldValue(targetPropName); if (!targetItemArray) { targetItemArray = []; this.setPropStoreFieldValue(targetPropName, targetItemArray); } // check if already exists, if so, remove the old one and append the new one (to have higher priority). var index = targetItemArray.indexOf(item); if (index >= 0) targetItemArray.splice(index, 1); targetItemArray.push(item); this.notifyPropSet(targetPropName, targetItemArray); //Kekule.ArrayUtils.pushUnique(targetItemArray, item); return this; }, /** @private */ _deleteOverrideRenderOptionItem: function(item, targetPropName) { var targetItemArray = this.getPropStoreFieldValue(targetPropName); if (!targetItemArray) { return; } var index = targetItemArray.indexOf(item); if (index >= 0) { //Kekule.ArrayUtils.remove(targetItemArray, item); targetItemArray.splice(index, 1); this.notifyPropSet(targetPropName, targetItemArray); } return this; }, /** * Add an override render option hash object to overrideRenderOptionItems. * @param {Hash} item */ addOverrideRenderOptionItem: function(item) { return this._appendOverrideRenderOptionItem(item, 'overrideRenderOptionItems'); }, /** * Add an override render 3D option hash object to overrideRender3DOptionItems. * @param {Hash} item */ addOverrideRender3DOptionItem: function(item) { return this._appendOverrideRenderOptionItem(item, 'overrideRender3DOptionItems'); }, /** * Remove an override render option hash object from overrideRenderOptionItems. * @param {Hash} item */ removeOverrideRenderOptionItem: function(item) { return this._deleteOverrideRenderOptionItem(item, 'overrideRenderOptionItems'); }, /** * Remove an override render option hash object from overrideRender3DOptionItems. * @param {Hash} item */ removeOverrideRender3DOptionItem: function(item) { return this._deleteOverrideRenderOptionItem(item, 'overrideRender3DOptionItems'); }, /** * Returns actual render settings, considering of overrideOptionItems. * @return {Hash} */ getOverriddenRenderOptions: function() { var renderOptions = this.getRenderOptions() || {}; var result = renderOptions; //var result = Object.create(this.getRenderOptions() || null); var overrideOptions = this.getOverrideRenderOptions(this.getOverrideRenderOptionItems()); //console.log('override options', this.getRenderOptions(), overrideOptions, this.getOverrideRenderOptionItems()); if (overrideOptions) { //result = renderOptions? Object.extend({}, renderOptions): {}; result = Object.extend(result, overrideOptions); } return result; }, /** * Returns actual 3D render settings, considering of overrideOptionItems. * @return {Hash} */ getOverriddenRender3DOptions: function() { var result = Object.create(this.getRender3DOptions() || null); var overrideOptions = this.getOverrideRenderOptions(this.getOverrideRender3DOptionItems()); if (overrideOptions) { result = Object.extend(result, overrideOptions); } return result; }, /** * Set value of a render option. * @param {String} prop * @param {Variant} value */ setRenderOption: function(prop, value) { var o = this.getRenderOptions(); if (!o) this.setRenderOptions({}); var o = this.getRenderOptions(); if (o[prop] !== value) { o[prop] = value; //console.log('render option set', prop); // notify change this.notifyPropSet('renderOptions', o); } }, /** * Set value of a 3D render option. * @param {String} prop * @param {Variant} value */ setRender3DOption: function(prop, value) { var o = this.getRender3DOptions(); if (!o) this.setRender3DOptions({}); var o = this.getRender3DOptions(); if (o[prop] !== value) { o[prop] = value; // notify change this.notifyPropSet('render3DOptions', o); } }, /** * Returns one render option value set by self only. * @param {String} prop * @returns {Variant} */ getRenderOption: function(prop) { var o = this.getOverriddenRenderOptions(); var result = (o? o[prop]: undefined); // || parentOpsValue; return result; }, /** * Returns one render 3D option value set by self only. * @param {String} prop * @returns {Variant} */ getRender3DOption: function(prop) { var o = this.getOverriddenRender3DOptions(); var result = (o? o[prop]: undefined); // || parentOpsValue; return result; }, /** * Returns one render option value set by self or inherited from parent. * @param {String} prop * @returns {Variant} */ getCascadedRenderOption: function(prop) { var o = this.getOverriddenRenderOptions(); var result = (o? o[prop]: undefined); // || parentOpsValue; if (Kekule.ObjUtils.isUnset(result)) { var parent = this.getParent? this.getParent(): null; var parentOpsValue = parent && parent.getCascadedRenderOption? parent.getCascadedRenderOption(prop): undefined; result = parentOpsValue; } return result; }, /** * Returns one render 3D option value set by self or inherited from parent. * @param {String} prop * @returns {Variant} */ getCascadedRender3DOption: function(prop) { var o = this.getOverriddenRender3DOptions(); var result = (o? o[prop]: undefined); // || parentOpsValue; if (Kekule.ObjUtils.isUnset(result)) { var parent = this.getParent? this.getParent(): null; var parentOpsValue = parent && parent.getCascadedRender3DOption? parent.getCascadedRender3DOption(prop): undefined; result = parentOpsValue; } return result; }, /** * Get base coord (coord that deciding the position) of object. Param coordMode determinate which coord (2D or 3D) will be returned. * @param {Int} coordMode * @param {Bool} allowCoordBorrow * @returns {Hash} */ getBaseCoord: function(coordMode, allowCoordBorrow) { var coordPos = this.getCoordPos(coordMode); var result = this.getCoordOfMode? this.getCoordOfMode(coordMode, allowCoordBorrow): null; if (result && coordPos !== Kekule.Render.CoordPos.CENTER) { if (coordPos === Kekule.Render.CoordPos.CORNER_TL) // now only handles 2D situation { var box = this.getExposedContainerBox(coordMode, allowCoordBorrow); var delta = {x: (box.x2 - box.x1) / 2, y: (box.y2 - box.y1) / 2}; if (coordMode === CM.COORD3D) delta.z = (box.z2 - box.z1) / 2; if (coordMode === CM.COORD3D) result = Kekule.CoordUtils.add(result, delta); else // 2D { result.x += delta.x; result.y -= delta.y; } } } return result; }, /** * Get center 2D coord of object. * @param {Bool} allowCoordBorrow * @returns {Hash} */ getBaseCoord2D: function(allowCoordBorrow) { return this.getBaseCoord(Kekule.CoordMode.COORD2D, allowCoordBorrow); }, /** * Get center 2D coord of object. * @param {Bool} * @returns {Hash} */ getBaseCoord3D: function(allowCoordBorrow) { return this.getBaseCoord(Kekule.CoordMode.COORD3D, allowCoordBorrow); }, /** * Get absolute center coord of object. Param coordMode determinate which coord (2D or 3D) will be returned. * @param {Int} coordMode * @param {Bool} allowCoordBorrow * @returs {Hash} */ getAbsBaseCoord: function(coordMode, allowCoordBorrow) { var coordPos = this.getCoordPos(coordMode); var result = this.getAbsCoordOfMode? this.getAbsCoordOfMode(coordMode, allowCoordBorrow): null; if (result && coordPos !== Kekule.Render.CoordPos.CENTER) { if (coordPos === Kekule.Render.CoordPos.CORNER_TL) // now only handles 2D situation { var box = this.getExposedContainerBox(coordMode, allowCoordBorrow); var delta = {x: (box.x2 - box.x1) / 2, y: (box.y2 - box.y1) / 2}; if (coordMode === CM.COORD3D) delta.z = (box.z2 - box.z1) / 2; if (coordMode === CM.COORD3D) result = Kekule.CoordUtils.add(result, delta); else // 2D { result.x += delta.x; result.y -= delta.y; } } } return result; }, /** * Get absolute center 2D coord of object. * @param {Bool} allowCoordBorrow * @returns {Hash} */ getAbsBaseCoord2D: function(allowCoordBorrow) { return this.getAbsBaseCoord(Kekule.CoordMode.COORD2D, allowCoordBorrow); }, /** * Get absolute center 3D coord of object. * @param {Bool} allowCoordBorrow * @returns {Hash} */ getAbsBaseCoord3D: function(allowCoordBorrow) { return this.getAbsBaseCoord(Kekule.CoordMode.COORD3D, allowCoordBorrow); }, /** * Set absolute center coord of object. * @param {Hash} value * @param {Int} coordMode */ setAbsBaseCoord: function(value, coordMode, allowCoordBorrow) { var coordPos = this.getCoordPos(coordMode); //var coord = Object.extend({}, value); var coord = Kekule.CoordUtils.clone(value); if (value && coordPos !== Kekule.Render.CoordPos.CENTER) { if (coordPos === Kekule.Render.CoordPos.CORNER_TL) // now only handles 2D situation { var box = this.getExposedContainerBox(coordMode, allowCoordBorrow); var delta = {x: (box.x2 - box.x1) / 2, y: (box.y2 - box.y1) / 2}; if (coordMode === CM.COORD3D) delta.z = (box.z2 - box.z1) / 2; if (coordMode === CM.COORD3D) coord = Kekule.CoordUtils.substract(coord, delta); else // 2D { coord.x -= delta.x; coord.y += delta.y; } } } if (this.setAbsCoordOfMode) this.setAbsCoordOfMode(coord, coordMode); else if (this.setCoordOfMode) this.setCoordOfMode(coord, coordMode); return this; }, setAbsBaseCoord2D: function(value, allowCoordBorrow) { return this.setAbsCoordOfMode(value, Kekule.CoordMode.COORD2D, allowCoordBorrow); }, setAbsBaseCoord3D: function(value, allowCoordBorrow) { return this.setAbsCoordOfMode(value, Kekule.CoordMode.COORD3D, allowCoordBorrow); }, /* * Returns abs center coord of object. * @param {Int} coordMode * @param {Bool} allowCoordBorrow * @returs {Hash} */ /* getAbsCenterCoord: function(coordMode, allowCoordBorrow) { var baseCoordPos = this.getCoordPos(coordMode); if (coordMode === Kekule.CoordMode.COORD2D && baseCoordPos === Kekule.Render.CoordPos.CORNER_TL) { var box = this.getExposedContainerBox(coordMode, allowCoordBorrow); var result = {'x': (box.x1 + box.x2) / 2, 'y': (box.y1 + box.y2) / 2}; return result; } else // center return getAbsBaseCoord(coordMode, allowCoordBorrow); }, */ /* * Set the abs center coord of object. * @param {Hash} value * @param {Int} coordMode * @param {Bool} allowCoordBorrow */ /* setAbsCenterCoord: function(value, coordMode, allowCoordBorrow) { var baseCoordPos = this.getCoordPos(coordMode); if (coordMode === Kekule.CoordMode.COORD2D && baseCoordPos === Kekule.Render.CoordPos.CORNER_TL) { var oldCenterCoord = this.getAbsCenterCoord(coordMode, allowCoordBorrow); var delta = Kekule.CoordUtils.substract(value, oldCoord); var oldBaseCoord = this.getAbsBaseCoord(coordMode, allowCoordBorrow); var newBaseCoord = Kekule.CoordUtils.add(oldBaseCoord, delta); this.setAbsBaseCoord(newBaseCoord, coordMode); } else // center { this.setAbsBaseCoord(value, coordMode); } return this; }, */ /** * If object is a child of subgroup, this function will return the nearest visible (displayed) ancestor. * @returns {Kekule.ChemStructureObject} */ getExposedAncestor: function() { var p = this.getParent(); if (!p) return this; else { if (p.isExpanded()) // parent is expanded, and its child are visible return this; else return p.getExposedAncestor(); } }, /** * If object is a child of subgroup, check if the subgroup is expanded and the object can be seen. * @returns {Bool} */ isExposed: function() { var p = this.getParent(); return (!p) || (p.isExpanded()) }, /** * If object is a sub group, check if it has expanded all its children to render. * @return {Bool} */ isExpanded: function() { return true; // normal atoms or connectors are always expanded (has no child structures) }, setExpanded: function(value) { //this.setRenderOption('expanded', value); // do nothing with normal atoms or connectors }, /** * Similiar to getLinkedObjs, but only with exposed ones. * @returns {Array} */ getLinkedExposedObjs: function() { var result = []; for (var i = 0, l = this.getLinkedConnectorCount(); i < l; ++i) { var connector = this.getLinkedConnectorAt(i); if (!connector.isExposed()) continue; var objs = connector.getConnectedExposedObjs(); for (var j = 0, k = objs.length; j < k; ++j) { if (objs[j] !== this) Kekule.ArrayUtils.pushUnique(result, objs[j]); } } return result; }, /** * Returns container box to contain this object with all its exposed children. * Descendants may override this method. * @param {Int} coordMode Determine to calculate 2D or 3D box. Value from {@link Kekule.CoordMode}. * @param {Bool} allowCoordBorrow * @returns {Hash} Box information. {x1, y1, z1, x2, y2, z2} (in 2D mode z1 and z2 will not be set). */ getExposedContainerBox: function(coordMode, allowCoordBorrow) { var result = this.getContainerBox(coordMode, allowCoordBorrow); return result; }, /** * Returns all length factors in object to help the scale ratio in autoscale mode. * To molecule, typically this is the all of bond lengths. * If no length can be found in this object, [] will be returned. * @param {Int} coordMode * @param {Bool} * @returns {Array} */ getAllAutoScaleRefLengths: function(coordMode, allowCoordBorrow) { return []; }, /** * Return all bonds in structure as well as in sub structure. * @returns {Array} Array of {Kekule.ChemStructureConnector}. */ getAllContainingConnectors: function() { if (this.getAllChildConnectors) return this.getAllChildConnectors(); else return []; }, /** * Returns median of all connector lengths. * @param {Int} coordMode * @param {Bool} allowCoordBorrow * @return {Float} */ getConnectorLengthMedian: function(coordMode, allowCoordBorrow) { var connectors = this.getAllContainingConnectors(); return Kekule.ChemStructureUtils.getConnectorLengthMedian(connectors, coordMode, allowCoordBorrow); } }); ClassEx.defineProps(Kekule.ChemObject, [ // explict set the coord position {'name': 'coordPos2D', 'dataType': DataType.INT, 'scope': Class.PropertyScope.PUBLIC, 'getter': function() { return this.getCoordPos(Kekule.CoordMode.COORD2D); } }, {'name': 'coordPos3D', 'dataType': DataType.INT, 'scope': Class.PropertyScope.PUBLIC, 'getter': function() { return this.getCoordPos(Kekule.CoordMode.COORD3D); } }, // new property, decide the 2D render style of a object (node or connector) {'name': 'renderOptions', 'dataType': DataType.OBJECT}, // new property, decide the 3D render style of a object (node or connector) {'name': 'render3DOptions', 'dataType': DataType.OBJECT}, {'name': 'overrideRenderOptionItems', 'dataType': DataType.ARRAY, 'scope': Class.PropertyScope.PUBLIC, 'serializable': false}, {'name': 'overrideRender3DOptionItems', 'dataType': DataType.ARRAY, 'scope': Class.PropertyScope.PUBLIC, 'serializable': false}, {'name': 'hidden', 'dataType': DataType.BOOL, 'scope': Class.PropertyScope.PRIVATE}, // use this to store the visible value, for backward compatibility {'name': 'visible', 'dataType': DataType.BOOL, 'serializable': false, 'getter': function() { return !this.getHidden(); }, 'setter': function(value) { if (!!value) this.setHidden(undefined); else this.setHidden(true); } } // controlling whether the object need to be rendered // new property, decide the interaction state of a object (node or connector), deprecated now //{'name': 'interactState', 'dataType': DataType.STRING} ]); ClassEx.extend(Kekule.ChemObjList, /** @lends Kekule.ChemObjList# */ { /** @ignore */ getAllContainingConnectors: function() { var result = []; for (var i = 0, l = this.getItemCount(); i < l; ++i) { var o = this.getItemAt(i); if (o.getAllContainingConnectors) result = result.concat(o.getAllContainingConnectors()); } return result; }, /** @ignore */ getAllAutoScaleRefLengths: function(coordMode, allowCoordBorrow) { var result = []; for (var i = 0, l = this.getItemCount(); i < l; ++i) { var o = this.getItemAt(i); if (o.getAllAutoScaleRefLengths) result = result.concat(o.getAllAutoScaleRefLengths(coordMode, allowCoordBorrow)); } return result; }, /** * Calculate the box to contain the exposed objects in list. * Descendants may override this method. * @param {Int} coordMode Determine to calculate 2D or 3D box. Value from {@link Kekule.CoordMode}. * @param {Bool} allowCoordBorrow * @returns {Hash} Box information. {x1, y1, z1, x2, y2, z2} (in 2D mode z1 and z2 will not be set). */ getExposedContainerBox: function(coordMode, allowCoordBorrow) { var result; var childObjs = this.toArray(); for (var i = 0, l = childObjs.length; i < l; ++i) { var obj = childObjs[i]; if (!obj.isExposed()) continue; var box = obj.getExposedContainerBox? obj.getExposedContainerBox(coordMode, allowCoordBorrow): obj.getContainerBox? obj.getContainerBox(coordMode, allowCoordBorrow): null; if (box) { if (!result) result = Kekule.BoxUtils.clone(box); //Object.extend({}, box); else result = Kekule.BoxUtils.getContainerBox(result, box); } } return result; } }); Kekule.ClassDefineUtils.addStandardCoordSupport(Kekule.ChemObjList); ClassEx.extend(Kekule.ChemSpaceElement, /** @lends Kekule.ChemSpaceElement# */ { /** @ignore */ getAllContainingConnectors: function() { return this.getChildren().getAllContainingConnectors(); }, /** @ignore */ getAllAutoScaleRefLengths: function(coordMode, allowCoordBorrow) { return this.getChildren().getAllAutoScaleRefLengths(coordMode, allowCoordBorrow); } }); ClassEx.extend(Kekule.ChemSpace, /** @lends Kekule.ChemSpace# */ { /** @ignore */ getAllContainingConnectors: function() { return this.getRoot().getAllContainingConnectors(); }, /** @ignore */ getAllAutoScaleRefLengths: function(coordMode, allowCoordBorrow) { var result; if (this.getDefAutoScaleRefLength()) result = [this.getDefAutoScaleRefLength()]; else result = this.getRoot().getAllAutoScaleRefLengths(coordMode, allowCoordBorrow); /* if (!result || !result.length) // no length found use default one { result = [this.getAutoScaleRefLength()]; } */ return result; } }); ClassEx.defineProps(Kekule.ChemObject, [ {'name': 'defAutoScaleRefLength', 'dataType': DataType.NUMBER, 'scope': Class.PropertyScope.PUBLIC} ]); ClassEx.extend(Kekule.BaseStructureConnector, /** @lends Kekule.BaseStructureConnector# */ { /** * Get center coord of object. Param coordMode determinate which coord (2D or 3D) will be returned. * @param {Int} coordMode * @param {Bool} allowCoordBorrow * @returns {Hash} */ getBaseCoord: function(coordMode, allowCoordBorrow) { var M = Kekule.CoordMode; var result = null; var coordCount = 0; var sum = (coordMode === M.COORD2D)? {'x': 0, 'y': 0}: {'x': 0, 'y': 0, 'z': 0}; for (var i = 0, l = this.getConnectedObjCount(); i < l; ++i) { var obj = this.getConnectedObjAt(i); var coord = obj.getBaseCoord? obj.getBaseCoord(coordMode, allowCoordBorrow): null; if (coord) { sum = Kekule.CoordUtils.add(sum, coord); ++coordCount; } } if (coordCount) result = Kekule.CoordUtils.divide(sum, coordCount); return result; }, /** * Get center absolute coord of object. * @param {Int} coordMode * @param {Bool} * @returns {Hash} */ getAbsBaseCoord: function(coordMode, allowCoordBorrow) { var M = Kekule.CoordMode; var result = null; var coordCount = 0; var sum = (coordMode === M.COORD2D)? {'x': 0, 'y': 0}: {'x': 0, 'y': 0, 'z': 0}; for (var i = 0, l = this.getConnectedObjCount(); i < l; ++i) { var obj = this.getConnectedObjAt(i); var coord = obj.getAbsBaseCoord? obj.getAbsBaseCoord(coordMode, allowCoordBorrow): null; if (coord) { sum = Kekule.CoordUtils.add(sum, coord); ++coordCount; } } if (coordCount) result = Kekule.CoordUtils.divide(sum, coordCount); return result; }, /** @ignore */ getAbsCoordOfMode: function(coordMode, allowCoordBorrow) { return this.getAbsBaseCoord(coordMode, allowCoordBorrow); }, /** * Returns only connected objects exposed to renderer. * @returns {Array} */ getConnectedExposedObjs: function() { var objs = this.getConnectedObjs(); //return objs; var result = []; for (var i = 0, l = objs.length; i < l; ++i) { var obj = objs[i].getExposedAncestor(); if (result.indexOf(obj) < 0) result.push(obj); } return result; }, /** * Return distance between two connected object. * If more than two objects are connected, this function will return null. * @param {Int} coordMode * @param {Bool} allowCoordBorrow * @returns {Float} */ getLength: function(coordMode, allowCoordBorrow) { var objs = this.getConnectedObjs(); if (objs.length !== 2) return null; else { var coord1 = objs[0].getAbsBaseCoord? objs[0].getAbsBaseCoord(coordMode, allowCoordBorrow): null; var coord2 = objs[1].getAbsBaseCoord? objs[1].getAbsBaseCoord(coordMode, allowCoordBorrow): null; return Kekule.CoordUtils.getDistance(coord1, coord2); } }, /** * Return 2D distance between two connected object. * If more than two objects are connected, this function will return null. * @param {Bool} allowCoordBorrow * @returns {Float} */ getLength2D: function(allowCoordBorrow) { return this.getLength(Kekule.CoordMode.COORD2D, allowCoordBorrow); }, /** * Return 3D distance between two connected object. * If more than two objects are connected, this function will return null. * @param {Bool} allowCoordBorrow * @returns {Float} */ getLength3D: function(allowCoordBorrow) { return this.getLength(Kekule.CoordMode.COORD3D, allowCoordBorrow); } }); ClassEx.defineProps(Kekule.BaseStructureConnector, [ { 'name': 'absCoord2D', 'dataType': DataType.HASH, 'serializable': false, 'getter': function(allowCoordBorrow) { return this.getAbsBaseCoord(Kekule.CoordMode.COORD2D, allowCoordBorrow); } }, { 'name': 'absCoord3D', 'dataType': DataType.HASH, 'serializable': false, 'getter': function(allowCoordBorrow) { return this.getAbsBaseCoord(Kekule.CoordMode.COORD3D, allowCoordBorrow); } } ]); ClassEx.extend(Kekule.ChemStructureConnector, /** @lends Kekule.ChemStructureConnector# */ { /** * Returns all length factors in object to help the scale ratio in autoscale mode. * To molecule, typically this is the all of bond lengths. * If no length can be found in this object, [] will be returned. * @param {Int} coordMode * @param {Bool} allowCoordBorrow * @returns {Array} */ getAllAutoScaleRefLengths: function(coordMode, allowCoordBorrow) { return [this.getLength(coordMode, allowCoordBorrow)]; } }); ClassEx.extend(Kekule.BaseStructureNode, /** @lends Kekule.BaseStructureNode# */ { /** * Get center coord of object. * @param {Int} coordMode * @param {Bool} allowCoordBorrow * @returns {Hash} */ getBaseCoord: function(coordMode, allowCoordBorrow) { return this.getCoordOfMode(coordMode, allowCoordBorrow); }, /** * Get absolute center coord of object. * @param {Int} coordMode * @param {Bool} allowCoordBorrow * @returns {Hash} */ getAbsBaseCoord: function(coordMode, allowCoordBorrow) { return this.getAbsCoordOfMode(coordMode, allowCoordBorrow); }, // Calculate the box to fit this object. getExposedContainerBox: function(coordMode, allowCoordBorrow) { var result = this.getContainerBox(coordMode, allowCoordBorrow); return result; }, getExposedContainerBox2D: function(allowCoordBorrow) { var result = this.getExposedContainerBox(Kekule.CoordMode.COORD2D, allowCoordBorrow); result.width = result.x2 - result.x1; result.height = result.y2 - result.y1; return result; }, getExposedContainerBox3D: function(allowCoordBorrow) { var result = this.getExposedContainerBox(Kekule.CoordMode.COORD3D, allowCoordBorrow); result.deltaX = result.x2 - result.x1; result.deltaY = result.y2 - result.y1; result.deltaZ = result.z2 - result.z1; return result; } }); ClassEx.extend(Kekule.ChemStructureNode, /** @lends Kekule.ChemStructureNode# */ { /** * Get label to display the atom. * @param {Int} hydrogenDisplayLevel Value from {@link Kekule.Render.HydrogenDisplayLevel}. * @param {Bool} showCharge Whether display charge of node. * @param {Kekule.Render.DisplayLabelConfigs} displayLabelConfigs */ getDisplayRichText: function(hydrogenDisplayLevel, showCharge, displayLabelConfigs, partialChargeDecimalsLength, chargeMarkType, distinguishSingletAndTripletRadical) { var R = Kekule.Render; if (Kekule.ObjUtils.isUnset(showCharge)) showCharge = true; var result = R.RichTextUtils.create(); var coreItem; var customLabel = this.getRenderOption('customLabel'); if (customLabel) { coreItem = R.RichTextUtils.strToRichText(customLabel); } else { coreItem = this.getCoreDisplayRichTextItem(hydrogenDisplayLevel, showCharge, displayLabelConfigs, partialChargeDecimalsLength); } if (coreItem) { //var coreAnchorItem = coreItem.anchorItem; // preserve previous core anchor if (showCharge) { coreItem = this.appendElectronStateDisplayText(coreItem, partialChargeDecimalsLength, chargeMarkType, distinguishSingletAndTripletRadical, displayLabelConfigs.getElectronicBiasMark()); } if (coreItem) { R.RichTextUtils.append(result, coreItem); //result.anchorItem = coreItem.anchorItem || coreItem; //result.anchorItem = coreAnchorItem || coreItem; result.anchorItem = coreItem; } } /* var charge = this.getCharge(); if (showCharge && charge) { var sCharge = ''; var chargeSign = (charge > 0)? '+': '-'; var chargeAmount = Math.abs(charge); if (chargeAmount != 1) sCharge += chargeAmount; sCharge += chargeSign; R.RichTextUtils.appendText(result, sCharge, {'textType': R.RichText.SUP}); } */ /* if (showCharge) result = this.appendElectronStateDisplayText(result); */ //console.log('rich text', result); return result; }, /** @private */ getCoreDisplayRichTextItem: function(hydrogenDisplayLevel, showCharge, displayLabelConfigs, partialChargeDecimalsLength){ // do nothing here, descendants need to override this method. return null; }, appendElectronStateDisplayText: function(coreItem, partialChargeDecimalsLength, chargeMarkType, distinguishSingletAndTripletRadical, electronicBiasMark) { var R = Kekule.Render; var charge = this.getCharge(); var electronicBias = this.getElectronicBias && this.getElectronicBias(); var radical = this.getRadical(); var section = R.ChemDisplayTextUtils.createElectronStateDisplayTextSection( charge, radical, partialChargeDecimalsLength, chargeMarkType, distinguishSingletAndTripletRadical, electronicBias, electronicBiasMark ); if (section) { //richText = R.RichTextUtils.append(richText, section); var group = R.RichTextUtils.createGroup(); R.RichTextUtils.append(group, coreItem); R.RichTextUtils.append(group, section); group.charDirection = Kekule.Render.TextDirection.LTR; group.anchorItem = coreItem; return group; } else return coreItem; } }); ClassEx.extend(Kekule.AbstractAtom, /** @lends Kekule.AbstractAtom# */ { /** * Get label to display the atom. * @param {Int} hydrogenDisplayLevel Value from {@link Kekule.Render.HydrogenDisplayLevel}. * @param {Bool} showCharge Whether display charge of node. */ getDisplayRichText: function(/*$super, */hydrogenDisplayLevel, showCharge, displayLabelConfigs, partialChargeDecimalsLength, chargeMarkType, distinguishSingletAndTripletRadical) { var R = Kekule.Render; if (Kekule.ObjUtils.isUnset(hydrogenDisplayLevel)) hydrogenDisplayLevel = R.HydrogenDisplayLevel.DEFAULT; /* //var result = this.getCoreDisplayRichText() || R.RichTextUtils.create(); var result = R.RichTextUtils.create(); var coreGroup = this.getCoreDisplayRichTextItem(); if (coreGroup) { R.RichTextUtils.append(result, coreGroup); result.anchorItem = coreGroup.anchorItem || coreGroup; } */ var result = this.tryApplySuper('getDisplayRichText', [hydrogenDisplayLevel, showCharge, displayLabelConfigs, partialChargeDecimalsLength, chargeMarkType, distinguishSingletAndTripletRadical]) /* $super(hydrogenDisplayLevel, showCharge, displayLabelConfigs, partialChargeDecimalsLength, chargeMarkType, distinguishSingletAndTripletRadical) */; var hcount = 0; switch (hydrogenDisplayLevel) { case R.HydrogenDisplayLevel.NONE: hcount = 0; break; case R.HydrogenDisplayLevel.ALL: hcount = this.getHydrogenCount(); break; case R.HydrogenDisplayLevel.EXPLICIT: hcount = this.getExplicitHydrogenCount(); break; case R.HydrogenDisplayLevel.UNMATCHED_EXPLICIT: { if (this.getImplicitHydrogenCount && (this.getImplicitHydrogenCount() !== this.getExplicitHydrogenCount())) hcount = this.getExplicitHydrogenCount(); break; } } if (hcount) { result = this.appendHydrogenDisplayText(result, hcount /* {'textType': R.RichText.SUB, 'charDirection': Kekule.Render.TextDirection.LTR} */ ); } return result; }, /** @private */ appendHydrogenDisplayText: function(richText, hcount) { var R = Kekule.Render; if (hcount) { if (hcount > 1) { var group = R.RichTextUtils.createGroup(); group.charDirection = Kekule.Render.TextDirection.LTR; // TODO: 'H' is fixed here, but actually it should be read from element symbol database. var item = R.RichTextUtils.appendText2(group, 'H'); R.RichTextUtils.appendText(group, hcount.toString(), { 'textType': R.RichText.SUB, 'refItem': item }); R.RichTextUtils.append(richText, group); } else // hcount == 1 R.RichTextUtils.appendText(richText, 'H'); } return richText; } }); ClassEx.extend(Kekule.Atom, /** @lends Kekule.Atom# */ { getCoreDisplayRichTextItem: function(hydrogenDisplayLevel, showCharge, displayLabelConfigs, partialChargeDecimalsLength) { var R = Kekule.Render; var result = R.RichTextUtils.createGroup(); // if isotope is assigned, use <sup>13</sup>C form or isotope alias (such as D), otherwise, use atom symbol only. var isotopeAlias = this.getIsotopeAlias(); var symbol = this.getSymbol(); if (isotopeAlias || this.getSymbol() !== K.Element.UNSET_ELEMENT) { var section; if (isotopeAlias && Kekule.ChemStructureNodeLabels.ENABLE_ISOTOPE_ALIAS) { section = R.RichTextUtils.createSection(this.getIsotopeAlias(), {'charDirection': Kekule.Render.TextDirection.LTR}); result = R.RichTextUtils.append(result, section); result.anchorItem = section; } else { section = R.RichTextUtils.createSection(this.getSymbol(), {'charDirection': Kekule.Render.TextDirection.LTR}); result = R.RichTextUtils.append(result, section); result.anchorItem = section; if (this.getMassNumber() !== Kekule.Isotope.UNSET_MASSNUMBER) { // mass number as superscript result = R.RichTextUtils.insertText(result, 0, this.getMassNumber().toString(), { 'textType': R.RichText.SUP, 'charDirection': Kekule.Render.TextDirection.LTR, 'refItem': result.anchorItem }); result.charDirection = Kekule.Render.TextDirection.LTR; } } } else // no element assigned result = R.RichTextUtils.appendText(result, (displayLabelConfigs && displayLabelConfigs.getUnsetElement()) || NL.UNSET_ELEMENT); return result; } }); ClassEx.extend(Kekule.Pseudoatom, /** @lends Kekule.Pseudoatom# */ { /** @ignore */ getCoreDisplayRichTextItem: function(hydrogenDisplayLevel, showCharge, displayLabelConfigs, partialChargeDecimalsLength) { var R = Kekule.Render; var s; var D = displayLabelConfigs; /* switch (this.getAtomType()) { case Kekule.PseudoatomType.DUMMY: s = (D && D.getDummyAtom()) || NL.DUMMY_ATOM; break; case Kekule.PseudoatomType.ANY: s = (D && D.getAnyAtom()) || NL.ANY_ATOM; break; case Kekule.PseudoatomType.HETERO: s = (D && D.getHeteroAtom()) || NL.HETERO_ATOM; break; default: // user custom s = this.getSymbol(); } */ s = this.getLabel(); if (!s) s = (D && D.getUnsetElement()) || NL.UNSET_ELEMENT; //return R.RichTextUtils.strToRichText(s); /* var result = R.RichTextUtils.createGroup(); R.RichTextUtils.appendText(result, s, null, true); // anchorItem */ var result = R.RichTextUtils.createSection(s, {'charDirection': Kekule.Render.TextDirection.LTR}); return result; } }); // turn a H2 like isotope id to display label rich text var appendIsotopeIdToRichText = function(richText, isotopeId, isAnchor) { var R = Kekule.Render; var detail = Kekule.IsotopesDataUtil.getIsotopeIdDetail(isotopeId); var massSection; if (detail.massNumber) massSection = R.RichTextUtils.appendText2(richText, detail.massNumber.toString(), {'textType': Kekule.Render.RichText.SUP, 'charDirection': Kekule.Render.TextDirection.LTR}); var symbolSection = R.RichTextUtils.appendText2(richText, detail.symbol, {'charDirection': Kekule.Render.TextDirection.LTR}, isAnchor); if (massSection) massSection.refItem = symbolSection; richText.charDirection = Kekule.Render.TextDirection.LTR; return richText; }; ClassEx.extend(Kekule.VariableAtom, /** @lends Kekule.VariableAtom# */ { /** @ignore */ getCoreDisplayRichTextItem: function(hydrogenDisplayLevel, showCharge, displayLabelConfigs, partialChargeDecimalsLength) { var R = Kekule.Render; var D = displayLabelConfigs; var result; if (this.isListEmpty()) { result = R.RichTextUtils.strToRichText((D && D.getVariableAtom()) || NL.VARIABLE_ATOM); result.charDirection = Kekule.Render.TextDirection.LTR; return result; } var isotopeIds = this.getAllowedIsotopeIds() || this.getDisallowedIsotopeIds(); var isDisallowed = this.isDisallowedList(); //var result = R.RichTextUtils.create(); result = R.RichTextUtils.createGroup(); result.charDirection = Kekule.Render.TextDirection.LTR; if (isDisallowed) result = R.RichTextUtils.appendText(result, D.getIsoListDisallowPrefix()); result = R.RichTextUtils.appendText(result, D.getIsoListLeadingBracket()); if (isotopeIds && isotopeIds.length) { for (var i = 0, l = isotopeIds.length; i < l; ++i) { var id = isotopeIds[i]; if (id) { if (i != 0) // not leading isotope, append delimiter result = R.RichTextUtils.appendText(result, D.getIsoListDelimiter()); result = appendIsotopeIdToRichText(result, id, i == 0); // first isotope is the anchor } } } result = R.RichTextUtils.appendText(result, D.getIsoListTailingBracket()); return result; } }); ClassEx.extend(Kekule.StructureConnectionTable, /** @lends Kekule.StructureConnectionTable# */ { // check if nodes inside has 2D coords exposedNodesHasCoord2D: function(allowCoordBorrow) { var nodes = this.getExposedNodes(); for (var i = 0, l = nodes.length; i < l; ++i) { if (nodes[i].hasCoord2D(allowCoordBorrow)) return true; } return false; }, exposedNodesHasCoord3D: function(allowCoordBorrow) { var nodes = this.getExposedNodes(); for (var i = 0, l = nodes.length; i < l; ++i) { if (nodes[i].hasCoord3D(allowCoordBorrow)) return true; } return false; }, // return all exposed nodes (including ones inside subgroups) getExposedNodes: function() { var result = []; var nodes = this.getNodes(); for (var i = 0, l = nodes.length; i < l; ++i) { var node = nodes[i]; if (node.getExposedNodes && node.isExpanded()) // is sub group and expanded { var subNodes = node.getExposedNodes(); result = result.concat(subNodes); } else if (node.isExposed()) result.push(node); } return result; }, // return all exposed connectors (including ones inside subgroups) getExposedConnectors: function() { var result = this.getConnectors(); var nodes = this.getNodes(); for (var i = 0, l = nodes.length; i < l; ++i) { var node = nodes[i]; if (node.getConnectors && node.isExpanded()) { var subConnectors = node.getExposedConnectors(); result = result.concat(subConnectors); } } return result; }, // Calculate the box to fit all exposed nodes in CTable. getExposedContainerBox: function(coordMode, allowCoordBorrow) { //console.log(this.getExposedNodes()); var nodes = this.getExposedNodes(); if (nodes.length) return this.getNodesContainBox(nodes, coordMode, allowCoordBorrow); else // no exposed nodes return null; }, getExposedContainerBox2D: function(allowCoordBorrow) { var result = this.getExposedContainerBox(Kekule.CoordMode.COORD2D, allowCoordBorrow); result.width = result.x2 - result.x1; result.height = result.y2 - result.y1; return result; }, getExposedContainerBox3D: function(allowCoordBorrow) { var result = this.getExposedContainerBox(Kekule.CoordMode.COORD3D, allowCoordBorrow); result.deltaX = result.x2 - result.x1; result.deltaY = result.y2 - result.y1; result.deltaZ = result.z2 - result.z1; return result; }, /** * Returns median of all connector lengths. * @param {Int} coordMode * @param {Bool} allowCoordBorrow * @return {Float} */ getConnectorLengthMedian: function(coordMode, allowCoordBorrow) { var connectors = this.getAllContainingConnectors(); /* var lengths = []; for (var i = 0, l = connectors.length; i < l; ++i) { var connector = connectors[i]; if (connector && connector.getLength) { var length = connector.getLength(coordMode, allowCoordBorrow); if (length) lengths.push(length); } } if (l === 0) // no connectors at all return 1; // TODO: this value should be calculated if (l <= 1) return lengths[0]; else { // sort lengths to find the median one lengths.sort(); var count = lengths.length; return (count % 2)? lengths[(count + 1) >> 1]: (lengths[count >> 1] + lengths[(count >> 1) + 1]) / 2; } */ return Kekule.ChemStructureUtils.getConnectorLengthMedian(connectors, coordMode, allowCoordBorrow); }, /** * Returns all length factors in object to help the scale ratio in autoscale mode. * To molecule, typically this is the all of bond lengths. * If no length can be found in this object, [] will be returned. * @param {Int} coordMode * @param {Bool} allowCoordBorrow * @returns {Array} */ getAllAutoScaleRefLengths: function(coordMode, allowCoordBorrow) { var connectors = this.getAllContainingConnectors(); var result = []; for (var i = 0, l = connectors.length; i < l; ++i) { var connector = connectors[i]; if (connector && connector.getAllAutoScaleRefLengths) { var lengths = connector.getAllAutoScaleRefLengths(coordMode, allowCoordBorrow); if (lengths) result = result.concat(lengths); } } return result; } }); ClassEx.extend(Kekule.ContentBlock, { /** @ignore */ getDefCoordPos: function(/*$super, */coordMode) { if (coordMode !== CM.COORD3D) return Kekule.Render.CoordPos.CORNER_TL; else return this.tryApplySuper('getDefCoordPos', [coordMode]) /* $super(coordMode) */; } }); ClassEx.extend(Kekule.ChemMarker.ChemPropertyMarker, { /** @ignore */ getDefCoordPos: function(/*$super, */coordMode) { if (coordMode !== CM.COORD3D) return Kekule.Render.CoordPos.CENTER; else return this.tryApplySuper('getDefCoordPos', [coordMode]) /* $super(coordMode) */; } }); ClassEx.extend(Kekule.StructureFragment, /** @lends Kekule.StructureFragment# */ { // StructureFragment may has child nodes and can be expanded isExpanded: function() { var o = this.getOverriddenRenderOptions(); return o? (!!o.expanded): false; }, setExpanded: function(value) { this.setRenderOption('expanded', value); }, // return all exposed nodes (including ones inside subgroups) getExposedNodes: function() { if (this.hasCtab()) return this.getCtab().getExposedNodes(); else return []; }, // return all exposed connectors (including ones inside subgroups) getExposedConnectors: function() { if (this.hasCtab()) return this.getCtab().getExposedConnectors(); else return []; }, // check if nodes inside has 2D coords exposedNodesHasCoord2D: function(allowCoordBorrow) { if (this.hasCtab()) return this.getCtab().exposedNodesHasCoord2D(a