UNPKG

kekule

Version:

Open source JavaScript toolkit for chemoinformatics

1,579 lines (1,529 loc) 146 kB
(function(){ "use strict"; var PS = Class.PropertyScope; var AU = Kekule.ArrayUtils; var KUnit = Kekule.Unit; Kekule.globalOptions.add('spectrum', { spectrumInfo: { enablePrefixOmissionInGetter: true }, data: { allowedComparisonErrorRate: 5e-8 } }); /** * A comparison flag, comparing the key properties and data of spectrum only. * @ignore */ Kekule.ComparisonMethod.SPECTRUM_DATA = 30; /** * Base namespace for spectra. * @namespace */ Kekule.Spectroscopy = {}; /** * Enumeration of data mode of spectrum variable. * @enum */ Kekule.Spectroscopy.DataMode = { /** Value points are continuous, e.g. in IR data table. */ CONTINUOUS: 1, /** Value points are discrete, e.g. in MS peak table. */ PEAK: 2 }; /** * Enumeration of peak position in spectrum data curve. * @enum */ Kekule.Spectroscopy.DataPeakPosition = { MAX: 0, MIN: 1 }; /** * Some util methods about spectroscopy. * @class */ Kekule.Spectroscopy.Utils = { /** * Returns the preferred peak position of spectrum. * @param {Variant} spectrumOrDataSection * @returns {Int} Value from {@link Kekule.Spectroscopy.DataPeakPosition}. */ getSpectrumPeakPosition: function(spectrumOrDataSection) { var stype = (spectrumOrDataSection.getSpectrumType)? spectrumOrDataSection.getSpectrumType(): (spectrumOrDataSection.getParentSpectrum() && spectrumOrDataSection.getParentSpectrum().getSpectrumType()); return (stype === Kekule.Spectroscopy.SpectrumType.IR)? Kekule.Spectroscopy.DataPeakPosition.MIN: Kekule.Spectroscopy.DataPeakPosition.MAX; }, /** * Expand range with new values. * @param {Hash} range * @param {Array} values */ expandDataRange: function(range, values) { var vs = AU.toArray(values); var r = range || {}; for (var i = 0, ii = vs.length; i < ii; ++i) { var value = vs[i]; var fields = Kekule.ObjUtils.getOwnedFieldNames(value); for (var j = 0, jj = fields.length; j < jj; ++j) { var fieldName = fields[j]; var fieldValue = value[fieldName]; if (!r[fieldName]) r[fieldName] = {'min': fieldValue, 'max': fieldValue}; else { if (fieldValue < r[fieldName].min) r[fieldName].min = fieldValue; else if (fieldValue > r[fieldName].max) r[fieldName].max = fieldValue; } } } return r; }, /** * Merge two data ranges. * Each item is a hash like {x: {min: minValue, max: maxValue}, y: {min: minValue, max: maxValue}}. * @param {Hash} r1 * @param {Hash} r2 * @returns {Hash} */ mergeDataRange: function(r1, r2) { var result = {}; var vars = AU.clone(Kekule.ObjUtils.getOwnedFieldNames(r1)); AU.pushUnique(vars, Kekule.ObjUtils.getOwnedFieldNames(r2)); for (var i = 0, l = vars.length; i < l; ++i) { var varSymbol =vars[i]; if (!r1[varSymbol]) result[varSymbol] = Object.extend(r2[varSymbol]); else if (!r2[varSymbol]) result[varSymbol] = Object.extend(r1[varSymbol]); else { result[varSymbol] = { 'min': (r1[varSymbol].min < r2[varSymbol].min)? r1[varSymbol].min: r2[varSymbol].min, 'max': (r1[varSymbol].max > r2[varSymbol].max)? r1[varSymbol].max: r2[varSymbol].max } } } return result; }, /** * Returns scale point information for a data range. * @param {Number} dataRangeMin * @param {Number} dataRangeMax * @param {Int} preferredScaleSectionCount * @returns {Hash} */ calcScalePointInfo: function(dataRangeMin, dataRangeMax, preferredScaleSectionCount) { if (preferredScaleSectionCount <= 0) preferredScaleSectionCount = 10; // avoid exception, set a default count value here var digitCounts = [Math.log10(Math.abs(dataRangeMin)), Math.log10(Math.abs(dataRangeMax))]; var digitCountMax = Math.floor(Math.max(digitCounts[0], digitCounts[1])); var digitCountMin = (Math.sign(dataRangeMin) === Math.sign(dataRangeMax))? Math.floor(Math.min(digitCounts[0], digitCounts[1], 0)): -Infinity; var useSciForm = (digitCountMax > 6); // need to use sci form if the digit num is very large to compact space var dataDelta = dataRangeMax - dataRangeMin; var deltaBetweenScales = dataDelta / preferredScaleSectionCount; var deltaBetweenScalesDigitCount = Math.max(Math.floor(Math.log10(Math.abs(deltaBetweenScales))), digitCountMin); var scaleBase = Math.pow(10, deltaBetweenScalesDigitCount); var actualDeltaBetweenScales; if (actualDeltaBetweenScales < 10 && dataDelta > 0.5) // major scale should be even number in 1-10 scope { actualDeltaBetweenScales = Math.ceil(actualDeltaBetweenScales / scaleBase / 2) * 2 * scaleBase; } else { actualDeltaBetweenScales = Math.ceil(deltaBetweenScales / scaleBase) * scaleBase; } var scaleFrom = Math.ceil(dataRangeMin / actualDeltaBetweenScales) * actualDeltaBetweenScales; var scaleTo = Math.floor(dataRangeMax / actualDeltaBetweenScales) * actualDeltaBetweenScales; var result = { 'useSciForm': useSciForm, 'scaleFrom': scaleFrom, 'scaleTo': scaleTo, 'scaleSectionCount': Math.round((scaleTo - scaleFrom) / actualDeltaBetweenScales), 'scaleValues': [], 'scaleBase': scaleBase, 'scaleFromOnBase': scaleFrom / scaleBase, 'scaleToOnBase': scaleTo / scaleBase, 'fixDigitsCountAfterPoint': Math.max(-deltaBetweenScalesDigitCount, 0) // record the recommended digits to appear after the decimal point }; for (var i = 0, l = result.scaleSectionCount + 1; i < l; ++i) { result.scaleValues.push(Math.round(i * actualDeltaBetweenScales / scaleBase) * scaleBase + scaleFrom); } //console.log(result, scaleBase); return result; }, /* * Calculate the standardized distance square between two data points. * The srcData/targetData/dataRanges should have the same dimension. * @param {Array} srcData * @param {Array} targetData * @param {Array} dataRangeValues Array of {min, max}. * @returns {Float} */ /* calcStandardizedDistanceSqr: function(srcData, targetData, dataRangeValues) { var length = dataRanges.length; for (var i = 0; i < length; ++i) { } }, */ /** * Generate a suitable label key for storing the spectrum info(meta/condition/parameter...) value. * @param {String} name The core name of key, e.g. observedFrequency. * @param {String} spectrumType The spectrum type, e.g. 'NMR'. * @param {String} namespace The namespace of this label, e.g. 'jcamp', 'cml'. * @returns {String} */ generateInfoKey: function(name, spectrumType, namespace) { var prefix = spectrumType || namespace; return prefix? prefix + MetaPropNamespace.DELIMITER + name: name; }, /** * Returns the default class to storing extra information for a data item in dataSection. * @param {Kekule.Spectroscopy.SpectrumDataSection} dataSection */ getDefaultDataExtraInfoClass: function(dataSection) { var mode = dataSection.getMode(); return (mode === Kekule.Spectroscopy.DataMode.PEAK)? Kekule.Spectroscopy.SpectrumPeakDetails: Kekule.Spectroscopy.SpectrumDataDetails; }, /** * Create a default object to storing extra information for a data item in dataSection. * @param {Kekule.Spectroscopy.SpectrumDataSection} dataSection */ createDefaultDataExtraInfoObject: function(dataSection) { var c = Kekule.Spectroscopy.Utils.getDefaultDataExtraInfoClass(dataSection); return c && new c(); } }; /** * Manager for namespaces of spectrum info property name. * @enum */ Kekule.Spectroscopy.MetaPropNamespace = { /** Namespace for custom properties */ CUSTOM: 'custom', /** Delimiter for namespace parts */ DELIMITER: '.', /** @private */ _namespaces: [], /** * Register namespace(s). * @param {Variant} namespace */ register: function(namespace) { AU.pushUnique(MetaPropNamespace._namespaces, namespace); }, /** * Unregister namespace(s). * @param {Variant} namespace */ unregister: function(namespace) { MetaPropNamespace._namespaces = AU.exclude(MetaPropNamespace._namespaces, namespace); }, /** * Returns all registered namespaces. * @returns {Array} */ getNamespaces: function() { return AU.clone(MetaPropNamespace._namespaces); }, /** * Returns a namespaced name of property. * @param {String} namespace * @param {String} coreName * @returns {string} */ createPropertyName: function(namespace, coreName) { return (namespace || '') + MetaPropNamespace.DELIMITER + coreName; }, /** * Returns the namespace/core part of a prop name. * @param {String} propName * @returns {Hash} A hash of {namespace, coreName}. */ getPropertyNameDetail: function(propName) { var namespace, coreName; var p = propName.lastIndexOf(MetaPropNamespace.DELIMITER); if (p >= 0) { namespace = propName.substring(0, p); coreName = propName.substr(p + 1); } else { coreName = propName; } return {'namespace': namespace, 'coreName': coreName}; }, /** * Returns the core part of a prop name. * @param {String} propName * @returns {String} */ getPropertyCoreName: function(propName) { return MetaPropNamespace.getPropertyNameDetail(propName).coreName; } }; /** @ignore */ var MetaPropNamespace = Kekule.Spectroscopy.MetaPropNamespace; MetaPropNamespace.register(MetaPropNamespace.CUSTOM); /** * A util object to manage the registered spectrum data value converters. * These converters are used to convert raw spectrum value from one unit to another (e.g., Hz to ppm in NMR). * @class */ Kekule.Spectroscopy.DataValueConverterManager = { /** @private */ _converters: [], /** * Register a converter object. * The converter object should implement the following methods: * { * convert: function(value, varDef, fromUnitObj, toUnitObj, spectrumDataSection, spectrum) => newValue, * canConvert: function(value, varDef, fromUnitObj, toUnitObj, spectrumDataSection, spectrum) => Bool, * getAltUnits: function(varDef, fromUnitObj, spectrumDataSection, spectrum) -> array (optional), returns the recommended alternative unitObjs for spectrum * } * @param {Object} converter */ register: function(converter) { DCM._converters.push(converter); }, /** * Unregister a converter. * @param {Object} converter */ unregister: function(converter) { var index = DMC._converters.indexOf(converter); if (index >= 0) DMC._converters.splice(index, 1); }, /** @private */ doConvert: function(value, varDef, fromUnit, toUnit, spectrumDataSection, spectrum) { if (fromUnit === toUnit) return value; if (!Kekule.NumUtils.isNormalNumber(value)) return value; var converters = DCM._converters; if (converters.length) { var fromUnitObj = Kekule.Unit.getUnit(fromUnit); var toUnitObj = Kekule.Unit.getUnit(toUnit); if (fromUnitObj && toUnitObj) { for (var i = converters.length - 1; i >= 0; --i) { var converter = converters[i]; if (converter.canConvert(value, varDef, fromUnitObj, toUnitObj, spectrumDataSection, spectrum)) return converter.convert(value, varDef, fromUnitObj, toUnitObj, spectrumDataSection, spectrum); } } } // no available converter found, can not convert Kekule.error(Kekule.$L('ErrorMsg.UNABLE_TO_CONVERT_BETWEEN_UNITS').format(fromUnitObj.getKey(), toUnitObj.getKey())); return null; }, /** @private */ getAltUnits: function(varDef, fromUnit, spectrumDataSection, spectrum) { var result = []; var converters = DCM._converters; if (converters.length) { var fromUnitObj = Kekule.Unit.getUnit(fromUnit); if (fromUnitObj) { for (var i = converters.length - 1; i >= 0; --i) { var converter = converters[i]; var subResult = converter.getAltUnits(varDef, fromUnitObj, spectrumDataSection, spectrum) || []; AU.pushUnique(result, subResult); } } } return result; } }; /** @ignore */ var DCM = Kekule.Spectroscopy.DataValueConverterManager; // register the default data value converter DCM.register({ convert: function(value, varDef, fromUnitObj, toUnitObj, spectrumDataSection, spectrum) { return fromUnitObj.convertValueTo(value, toUnitObj); }, canConvert: function(value, varDef, fromUnitObj, toUnitObj, spectrumDataSection, spectrum) { return fromUnitObj.canConvertValueTo(toUnitObj); }, getAltUnits: function(varDef, fromUnitObj, spectrumDataSection, spectrum) { var category = fromUnitObj.category; return category.getConvertableUnits(); } }); // register a converter to convert between NMR frequency and ppm DCM.register({ convert: function(value, varDef, fromUnitObj, toUnitObj, spectrumDataSection, spectrum) { var observeFreq = spectrum.getParameter('NMR.ObserveFrequency'); if (fromUnitObj.category === KUnit.Frequency) // from Hz to ppm { var freq = fromUnitObj.convertValueTo(value, observeFreq.getUnit()); var pureRatio = freq / observeFreq.getValue(); // in ppm * 1e10, in another word, the pure ratio return KUnit.Dimensionless.ONE.convertValueTo(pureRatio, toUnitObj); } else if (fromUnitObj.category === Kekule.Unit.Dimensionless) // from ppm to Hz { var value2 = fromUnitObj.convertValueToStandard(value); var freq = value2 * observeFreq.getValue(); var freqUnit = KUnit.getUnit(observeFreq.getUnit()); return freqUnit.convertValueTo(freq, toUnitObj); } }, canConvert: function(value, varDef, fromUnitObj, toUnitObj, spectrumDataSection, spectrum) { if (spectrum.getSpectrumType() === Kekule.Spectroscopy.SpectrumType.NMR) { var observeFreq = spectrum.getParameter('NMR.ObserveFrequency'); var freqNum = observeFreq && observeFreq.getValue(); if (observeFreq && freqNum && Kekule.NumUtils.isNormalNumber(freqNum) && Kekule.Unit.getUnit(observeFreq.getUnit()).category === Kekule.Unit.Frequency) { return (fromUnitObj.category === Kekule.Unit.Frequency && toUnitObj.category === Kekule.Unit.Dimensionless) || (fromUnitObj.category === Kekule.Unit.Dimensionless && toUnitObj.category === Kekule.Unit.Frequency); } } return false; }, getAltUnits: function(varDef, fromUnitObj, spectrumDataSection, spectrum) { var result = []; if (spectrum.getSpectrumType() === Kekule.Spectroscopy.SpectrumType.NMR) { var observeFreq = spectrum.getParameter('NMR.ObserveFrequency'); var freqNum = observeFreq && observeFreq.getValue(); if (observeFreq && freqNum && Kekule.NumUtils.isNormalNumber(freqNum) && Kekule.Unit.getUnit(observeFreq.getUnit()).category === Kekule.Unit.Frequency) { if (fromUnitObj.category === Kekule.Unit.Frequency) result.push(Kekule.Unit.Dimensionless.PARTS_PER_MILLION); else if (fromUnitObj.category === Kekule.Unit.Dimensionless) result = result.concat(Kekule.Unit.Frequency.getConvertableUnits()); } } return result; } }); // register a converter to convert between IR wave length and wave number DCM.register({ convert: function(value, varDef, fromUnitObj, toUnitObj, spectrumDataSection, spectrum) { if (fromUnitObj.category === KUnit.Length) // from wave length to wave number { var standardWaveLengthScalar = fromUnitObj.convertValueToStandardEx(value); var standardWaveNumber = 1 / standardWaveLengthScalar.value; return toUnitObj.convertValueFromStandard(standardWaveNumber); } else if (fromUnitObj.category === KUnit.WaveNumber) // from wave number to wave length { var standardWaveNumberScalar = fromUnitObj.convertValueToStandardEx(value); var standardWaveLength = 1 / standardWaveNumberScalar.value; return toUnitObj.convertValueFromStandard(standardWaveLength); } }, canConvert: function(value, varDef, fromUnitObj, toUnitObj, spectrumDataSection, spectrum) { if (spectrum.getSpectrumType() === Kekule.Spectroscopy.SpectrumType.IR) { return (fromUnitObj.category === Kekule.Unit.Length && toUnitObj.category === Kekule.Unit.WaveNumber) || (fromUnitObj.category === Kekule.Unit.WaveNumber && toUnitObj.category === Kekule.Unit.Length); } return false; }, getAltUnits: function(varDef, fromUnitObj, spectrumDataSection, spectrum) { var result; if (spectrum.getSpectrumType() === Kekule.Spectroscopy.SpectrumType.IR) { if (fromUnitObj.category === Kekule.Unit.Length) result = [Kekule.Unit.WaveNumber.RECIPROCAL_CENTIMETER]; else if (fromUnitObj.category === Kekule.Unit.WaveNumber) result = [Kekule.Unit.Length.getConvertableUnits()]; } return result; } }); /** * Variable used in spectrum. * @class * @augments Kekule.VarDefinition * * @property {String} internalUnit Unit that used in internal data storage. * @property {String} externalUnit Unit that used to expose data to public. */ Kekule.Spectroscopy.SpectrumVarDefinition = Class.create(Kekule.VarDefinition, /** @lends Kekule.Spectroscopy.SpectrumVarDefinition# */ { /** @private */ CLASS_NAME: 'Kekule.Spectroscopy.SpectrumVarDefinition', initProperties: function() { this.defineProp('internalUnit', {'dataType': DataType.STRING, 'serializable': false, 'getter': function() { return this.getUnit(); }, 'setter': function(value) { this.setUnit(value); } }); this.defineProp('externalUnit', {'dataType': DataType.STRING}); }, /** * Returns the actual external unit of var. * Usually this function returns the value of {@link Kekule.Spectroscopy.SpectrumVarDefinition.externalUnit} * If it is not set, the result will be the same as internalUnit. * @returns {String} */ getActualExternalUnit: function() { return this.getExternalUnit() || this.getInternalUnit(); }, /** * Whether the external unit setting of this var differs from the internal unit. * @returns {Bool} */ hasDifferentExternalUnit: function() { var externalUnit = this.getExternalUnit(); return !!(externalUnit && externalUnit !== this.getInternalUnit()); } }); /** * Represent part of data in a spectrum. * @class * * @param {String} name * @param {Kekule.Spectroscopy.SpectrumData} parent Parent spectrum data object. * @param {Array} localVariables Array of variable definition objects or symbols. * * @property {Kekule.Spectroscopy.SpectrumData} parent Parent spectrum data object. * @property {Array} localVarInfos Stores the local variable information. Each item is a hash containing fields {'symbol', 'range'(optional)}. * @property {Array} varSymbols Array of variable symbols such as ['X', 'Y']. * @property {Int} mode Data mode of section, continuous or peak. * @property {Date} modifiedTime Time that do the last modification to data. * @property {Hash} peakRoot * @property {String} name * @property {String} title */ Kekule.Spectroscopy.SpectrumDataSection = Class.create(Kekule.ChemObject, /** @lends Kekule.Spectroscopy.SpectrumDataSection# */ { /** @private */ CLASS_NAME: 'Kekule.Spectroscopy.SpectrumDataSection', /** @private */ DATAITEM_SRC_FIELD_NAME: '_src', /** @private */ DATAITEM_EXTRA_FIELD_NAME: '_extra', /** @private */ initialize: function(name, parent, localVariables) { this.updateDataModifiedTime(); this.setPropStoreFieldValue('name', name); this.setPropStoreFieldValue('localVarInfos', []); this.setPropStoreFieldValue('dataItems', []); this.setPropStoreFieldValue('parent', parent); this.tryApplySuper('initialize', []); //this.setLocalVarSymbols(localVariables); if (localVariables) this.setLocalVariables(localVariables); this.setDataSorted(true); this._cache = {}; // private //this.setPropStoreFieldValue('variables', variables? AU.clone(variables): []); }, doFinalize: function() { if (this.getParent() && this.getParent().removeChild) { // remove item in parent first this.getParent().removeChild(this); } this.clear(); var variables = this.getVariables(); for (var i = 0, l = variables.length; i < l; ++i) { variables[i].finalize(); } this.setPropStoreFieldValue('localVarInfos', null); this.tryApplySuper('doFinalize'); }, /** @private */ initProperties: function() { this.defineProp('parent', {'dataType': 'Kekule.MapEx', 'setter': null, 'serializable': false}); this.defineProp('name', {'dataType': DataType.STRING}); this.defineProp('title', {'dataType': DataType.STRING}); /* this.defineProp('variables', {'dataType': DataType.ARRAY}); this.defineProp('varSymbols', {'dataType': DataType.ARRAY, 'setter': null, 'scope': PS.PRIVATE, 'getter': function() { var result = []; var list = this.getVariables(); for (var j = 0, jj = list.length; j < jj; ++j) { var varDef = list[j]; result.push(varDef.getSymbol()); } return result; }}); */ this.defineProp('localVarInfos', {'dataType': DataType.ARRAY, 'setter': null}); this.defineProp('localVarSymbols', {'dataType': DataType.ARRAY, 'scope': PS.PRIVATE, 'serializable': false, 'getter': function() { var result = []; var list = this.getActualLocalVarInfos(); if (list && list.length) { for (var j = 0, jj = list.length; j < jj; ++j) { var info = list[j]; //result.push(info.varDef.getSymbol()); result.push(info.symbol); } } /* else // localVarInfos is not initialized yet, read from the storage result = this.getPropStoreFieldValue('localVarSymbols'); */ return result; }, 'setter': function(value) { var v = value || []; //this.setPropStoreFieldValue('localVarSymbols', v); this._updateLocalVarInfosFromSymbols(v); } }); this.defineProp('mode', {'dataType': DataType.INT, 'enumSource': Kekule.Spectroscopy.DataMode, 'setter': function(value) { if (this.getMode() !== value) { //console.log('set mode', value); this.setPropStoreFieldValue('mode', value); this.notifyDataChange(); } } }); this.defineProp('defPeakRoot', {'dataType': DataType.Hash}); this.defineProp('modifiedTime', {'dataType': DataType.DATE, 'setter': null}); // private, stores the data items, each item is a hash, e.g. {x: 1, y: 10, w: 2} this.defineProp('dataItems', {'dataType': DataType.ARRAY, 'setter': null, 'scope': PS.PRIVATE}); }, /** @ignore */ initPropValues: function() { this.tryApplySuper('initPropValues'); this.setMode(Kekule.Spectroscopy.DataMode.CONTINUOUS); }, /** @ignore */ getAutoIdPrefix: function() { return 'sec'; }, /** @ignore */ ownerChanged: function(newOwner, oldOwner) { // change the owner of all extra info objects if possible for (var i = 0, l = this.getDataCount(); i < l; ++i) { var extra = this.getExtraInfoAt(i); if (extra && extra.setOwner) extra.setOwner(newOwner); } this.tryApplySuper('ownerChanged', [newOwner, oldOwner]); }, // custom save / load method /** @ignore */ doSaveProp: function(obj, prop, storageNode, options, serializer, rootObj, rootStorageNode, handledObjs) { if (!prop.serializable) return; var propName = prop.name; if (propName === 'dataItems') { var node = serializer.createChildStorageNode(storageNode, serializer.propNameToStorageName('dataItems'), false); var subNode = serializer.createChildStorageNode(node, serializer.propNameToStorageName('values'), true); // create sub node for array serializer.saveObj(obj.getDataItems(), subNode, options, rootObj, rootStorageNode, handledObjs); // save array values in this sub node // extract all extra info of data array and save them var extraInfos = obj._extractAllExtraInfoOfDataItems(); if (extraInfos.length) { var subNode = serializer.createChildStorageNode(node, serializer.propNameToStorageName('extras'), true); serializer.saveObj(extraInfos, subNode, options, rootObj, rootStorageNode, handledObjs); } return true; // this property is handled, do not use default save method } else return false; // use the default method }, /** @ignore */ doLoadProp: function(obj, prop, storageNode, serializer, rootObj, rootStorageNode, handledObjs) { if (!prop.serializable) return; var propName = prop.name; if (propName === 'dataItems') { var items = []; var node = serializer.getChildStorageNode(storageNode, serializer.propNameToStorageName('dataItems')); var subNode = serializer.getChildStorageNode(node, serializer.propNameToStorageName('values')); // get sub node for array serializer.loadObj(items, subNode, rootObj, rootStorageNode, handledObjs); obj.setPropStoreFieldValue('dataItems', items); // then the extra info var subNode = serializer.getChildStorageNode(node, serializer.propNameToStorageName('extras')); if (subNode) { var extras = []; serializer.loadObj(extras, subNode, rootObj, rootStorageNode, handledObjs); obj._writeExtraInfoOfDataItems(extras); } return true; } else return false; // use the default method }, /** @private */ _setSysFieldOfDataItem: function(dataItem, fieldName, fieldValue) { try { if (Object.defineProperty) { Object.defineProperty(dataItem, fieldName, { 'value': fieldValue, 'configurable': true, 'writable': true, 'enumerable': false }); } else dataItem[fieldName] = fieldValue; } catch(e) { dataItem[fieldName] = fieldValue; } }, /** @private */ _extractAllExtraInfoOfDataItems: function() { var result = []; for (var i = 0, l = this.getDataCount(); i < l; ++i) { var info = this.getExtraInfoAt(i); if (info) result.push({'index': i, 'info': info}); } return result; }, /** @private */ _writeExtraInfoOfDataItems: function(extras) { for (var i = 0, l = extras.length; i < l; ++i) { var info = extras[i]; this.setExtraInfoAt(info.index, info.info); } }, /* @ignore */ /* parentChanged: function(newParent, oldParent) { //console.log('parent changed', newParent && newParent.getClassName(), oldParent); var result = this.tryApplySuper('parentChanged', newParent, oldParent); // after changing of parent the local var info may be changed as well this._updateLocalVarInfosFromSymbols(this.getLocalVarSymbols()); return result; }, */ /** @ignore */ isEmpty: function() { return this.getDataCount() <= 0; }, /** * Returns whether this data section containing the peak data. * @returns {Bool} */ isPeakSection: function() { return this.getMode() === Kekule.Spectroscopy.DataMode.PEAK; }, /** * Returns whether this data section containing the peak data and assignments. * @returns {Bool} */ hasPeakAssignments: function() { if (this.isPeakSection()) { var result = false; for (var i = 0, l = this.getDataCount(); i < l; ++i) { var extra = this.getExtraInfoAt(i); if (extra && extra && extra.hasAssignments && extra.hasAssignments()) { result = true; break; } } return result; } else return false; }, /** * Returns the actual parent SpectrumData object. * @returns {Kekule.Spectroscopy.Spectrum} * @private */ getParentSpectrum: function() { var p = this.getParent(); while (p && !(p instanceof Kekule.Spectroscopy.Spectrum) && p.getParent) { p = p.getParent(); } return p; }, /** * Returns the variable definition of parent spectrum data. * @returns {Array} */ getParentVariables: function() { var parent = this.getParentSpectrum(); return (parent && parent.getVariables()) || []; }, /** * Returns the actual local variable infos. * User should use this method rather than ref to localVarInfos property. * @returns {Array} */ getActualLocalVarInfos: function() { var result = AU.clone(this.getLocalVarInfos()); if (!result || !result.length) // inherit all from parent spectrum { var vars = this.getParentVariables(); for (var i = 0, l = vars.length; i < l; ++i) { result.push({'symbol': vars[i].symbol}); } } return result; }, /** @private */ _updateLocalVarInfosFromSymbols: function(varSymbols, silent) { var v = varSymbols || []; var infos = []; var parent = this.getParentSpectrum(); for (var i = 0, l = v.length; i < l; ++i) { var item = v[i]; this._pushLocalVariable(parent, item, infos); } //console.log('update local var infos', varSymbols, infos, parent); this.setPropStoreFieldValue('localVarInfos', infos); //this.setLocalVarInfos(infos); this.notifyPropSet('localVarInfos', infos, silent); }, /** @private */ _pushLocalVariable: function(parent, varSymbol, targetArray) { if (!targetArray) targetArray = this.getLocalVarInfos(); //var parent = this.getParent(); if (parent && parent.getVariable) { var varDef = parent.getVariable(varSymbol); if (varDef) { targetArray.push({/*'varDef': varDef,*/ 'symbol': varSymbol}); } } }, /** * Set the local variable symbols or definitions. * @param {Array} variables Array of var defintion or symbols. */ setLocalVariables: function(variables) { var localVar; var varDefs = [], varSymbols = []; for (var i = 0, l = variables.length; i < l; ++i) { localVar = variables[i]; if (typeof(localVar) === 'string') // a var symbol { varSymbols.push(localVar); } else // var definition { varDefs.push(localVar); } } if (varDefs.length) { this.setPropStoreFieldValue('localVarInfos', varDefs); this.notifyPropSet('localVarInfos', varDefs); } else if (varSymbols.length) { this._updateLocalVarInfosFromSymbols(varSymbols); } }, /** * Returns the local variable information index of variable. * @param {Variant} varIndexOrNameOrDef * @returns {Int} */ getLocalVarInfoIndex: function(varIndexOrNameOrDef) { var result = -1; var localVarInfos = this.getActualLocalVarInfos(); if (typeof (varIndexOrNameOrDef) === 'number') result = varIndexOrNameOrDef; else // if (varIndexOrNameOrDef instanceof Kekule.Spectroscopy.SpectrumVarDefinition) { var symbol = varIndexOrNameOrDef.getSymbol? varIndexOrNameOrDef.getSymbol(): varIndexOrNameOrDef; for (var i = 0, l = localVarInfos.length; i < l; ++i) { /* var varDef = localVarInfos[i].varDef; if (varDef === varIndexOrNameOrDef || varDef.getSymbol() === varIndexOrNameOrDef) { result = i; break; } */ if (symbol === localVarInfos[i].symbol) { result = i; break; } } } return result; }, /** * Returns the local information of variable. * @param {Variant} varIndexOrNameOrDef * @returns {Hash} */ getLocalVarInfo: function(varIndexOrNameOrDef) { var index = this.getLocalVarInfoIndex(varIndexOrNameOrDef); var result = (index >= 0)? this.getActualLocalVarInfos()[index]: null; /* if (result) { var parent = this.getParentSpectrum(); if (parent) { var symbol = result.symbol; result = Object.create(result); // avoid affect the original hash object result.varDef = parent.getVariable(symbol); } } */ return result; /* var result; var localVarInfos = this.getActualLocalVarInfos(); if (typeof (varIndexOrNameOrDef) === 'number') result = localVarInfos[varIndexOrNameOrDef]; else // if (varIndexOrNameOrDef instanceof Kekule.Spectroscopy.SpectrumVarDefinition) { for (var i = 0, l = localVarInfos.length; i < l; ++i) { var varDef = localVarInfos[i].varDef; if (varDef === varIndexOrNameOrDef || varDef.getSymbol() === varIndexOrNameOrDef) { result = localVarInfos[i]; break; } } } return result; */ }, /** * Returns the local information value of a variable. * @param {Variant} varIndexOrNameOrDef * @param {String} key * @returns {Variant} */ getLocalVarInfoValue: function(varIndexOrNameOrDef, key) { var info = this.getLocalVarInfo(varIndexOrNameOrDef); return info && info[key]; }, /** * Set a local information of variable. * @param {Variant} varIndexOrNameOrDef * @param {String} key * @param {Variant} value */ setLocalVarInfoValue: function(varIndexOrNameOrDef, key, value) { var info = this.getLocalVarInfo(varIndexOrNameOrDef); info[key] = value; }, /** * Returns the variable definition of a local variable. * @param {Variant} varIndexOrNameOrDef * @returns {Kekule.Spectroscopy.SpectrumVarDefinition} */ getLocalVarDef: function(varIndexOrNameOrDef) { //return this.getLocalVarInfoValue(varIndexOrNameOrDef, 'varDef'); var symbol = this.getLocalVarInfoValue(varIndexOrNameOrDef, 'symbol'); var parent = this.getParentSpectrum(); return parent && parent.getVariable(symbol); }, /** * Returns the local variable info of certain dependency. * @param {Int} dependency * @returns {Array} */ getLocalVarInfoOfDependency: function(dependency) { var result = []; var localVarInfos = this.getActualLocalVarInfos(); for (var i = 0, l = localVarInfos.length; i < l; ++i) { var varDef = this.getLocalVarDef(i); if (varDef.getDependency() === dependency) { var info = Object.extend({}, localVarInfos[i]); info.varDef = varDef; result.push(info); } } return result; }, /** * Returns the local variable symbols of certain dependency. * @param {Int} dependency * @returns {Array} */ getLocalVarSymbolsOfDependency: function(dependency) { var varInfos = this.getLocalVarInfoOfDependency(dependency); var result = []; for (var i = 0, l = varInfos.length; i < l; ++i) { result.push(varInfos[i].varDef.getSymbol()); } return result; }, /** * Returns the from/to value of a continuous variable. * Note the return values of this function are all based on internal var unit. * @param {Variant} varNameOrIndexOrDef * @returns {Hash} Hash of {fromValue, toValue} */ getContinuousVarRange: function(varIndexOrNameOrDef) { var parent = this.getParent(); var varInfo = this.getLocalVarInfo(varIndexOrNameOrDef); return varInfo.continuousRange || (parent && parent.getContinuousVarRange && parent.getContinuousVarRange(varInfo.symbol)); /* var result = this.getLocalVarInfoValue(varIndexOrNameOrDef, 'continuousRange'); if (!result) { var parent = this.getParent(); result = parent && parent.getContinuousVarRange(varInfo.varDef); } return result; */ }, /** * Set the from/to value of a variable and mark it as a continuous one. * @param {Variant} varNameOrIndexOrDef * @param {Number} fromValue * @param {Number} toValue */ setContinuousVarRange: function(varIndexOrNameOrDef, fromValue, toValue) { /* var varInfo = this.getLocalVarInfo(varIndexOrNameOrDef); varInfo.range = {'fromValue': fromValue, 'toValue': toValue}; */ this.setLocalVarInfoValue(varIndexOrNameOrDef, 'continuousRange', {'fromValue': fromValue, 'toValue': toValue}); return this; }, /** * Remove the continuous information of a variable. * @param {Variant} varIndexOrNameOrDef */ clearContinuousVarRange: function(varIndexOrNameOrDef) { /* var varInfo = this.getLocalVarInfo(varIndexOrNameOrDef); varInfo.range = null; */ this.setLocalVarInfoValue(varIndexOrNameOrDef, 'continuousRange', null); return this; }, /** * Set the local default value of a variable when the concrete value in spectrum is absent. * @param {Variant} varIndexOrNameOrDef * @param {Number} value */ setDefaultVarValue: function (varIndexOrNameOrDef, value) { this.setLocalVarInfoValue(varIndexOrNameOrDef, 'defaultValue', value); return this; }, /** * Clear the local default value of a variable. * @param {Variant} varIndexOrNameOrDef */ clearDefaultVarValue: function(varIndexOrNameOrDef) { return this.setDefaultVarValue(varIndexOrNameOrDef, null); }, /** * Get the local default value of a variable when the concrete value in spectrum is absent. * @param {Variant} varIndexOrNameOrDef * @returns {Number} */ getDefaultVarValue: function(varIndexOfNameOrDef) { var result = this.getLocalVarInfoValue(varIndexOfNameOrDef, 'defaultValue'); if (Kekule.ObjUtils.isUnset(result)) { var varInfo = this.getLocalVarInfo(varIndexOfNameOrDef); var parent = this.getParent(); result = parent && parent.getDefaultVarValue(varInfo.symbol); } return result; }, /** * Returns the range when displaying spectrum of a variable. * @param {Variant} varNameOrIndexOrDef * @param {Hash} options May include fields: * { * autoCalc: Bool. If true, when explicit display range is not set, the number range of variable will be calculated and returned. * basedOnInternalUnit: Bool. If true, the returned value will be based on internal unit rather than the external unit of variable. * } * @returns {Hash} Hash of {min, max} */ getVarDisplayRange: function(varIndexOrNameOrDef, options) { var op = options || {}; //var varDef = this.getVar var varIndex = this.getLocalVarInfoIndex(varIndexOrNameOrDef); var info = this.getLocalVarInfo(varIndex); var result = info.displayRange? Object.extend({}, info.displayRange): null; // avoid affect the original values if (!result) // check the var definition { //var varDef = info.varDef; var varDef = this.getLocalVarDef(varIndex); var varDefRange = varDef.getInfoValue('displayRange'); if (varDefRange) result = Object.extend({}, varDefRange); // avoid affecting the original values } if (!result && op.autoCalc) result = this.calcDataRange(varIndex, {basedOnInternalUnit: true})[info.symbol]; // get range with internal unit first //result = this.calcDataRange(varIndexOrNameOrDef)[info.varDef.getSymbol()]; // do not forget to do unit conversion if necessary if (!op.basedOnInternalUnit) { result = this._convertDataRangeToExternalUnit(result, varIndex); /* var fieldNames = Kekule.ObjUtils.getOwnedFieldNames(result); for (var i = 0, l = fieldNames.length; i < l; ++i) { var fname = fieldNames[i]; //result[fname] = this._convertVarValueToExternal(result[fname], varIndex); } // after conversion, the min/max values may be reversed if (result && result.min > result.max) { var temp = result.min; result.min = result.max; result.max = temp; } */ } return result; }, /** * Set the range when displaying spectrum of a variable. * @param {Variant} varNameOrIndexOrDef * @param {Number} minValue * @param {Number} maxValue * @param {Hash} options Extra options, may include fields: * { * basedOnExternalUnit: Bool * } */ setVarDisplayRange: function(varIndexOrNameOrDef, minValue, maxValue, options) { var op = options || {}; var range = {'min': minValue, 'max': maxValue}; if (op.basedOnExternalUnit) // need to convert values to internal unit first { var varIndex = this.getLocalVarInfoIndex(varIndexOrNameOrDef); range = this._convertDataRangeToInternalUnit(range, varIndex); } this.setLocalVarInfoValue(varIndexOrNameOrDef, 'displayRange', range); return this; }, /** * Remove the display range information of a variable. * @param {Variant} varIndexOrNameOrDef */ clearVarDisplayRange: function(varIndexOrNameOrDef) { this.setLocalVarInfoValue(varIndexOrNameOrDef, 'displayRange',null); return this; }, /** * Returns display range of variables. * @param {Array} targetVariables Array of variable definition or symbol. * If not set, all variables will be considered. * @param {Hash} options May include fields: * { * autoCalc: Bool. If true, when explicit display range is not set, the number range of variable will be calculated and returned. * basedOnInternalUnit: Bool. If true, the returned value will be based on internal unit rather than the external unit of variable. * } * @returns {Hash} */ getDisplayRangeOfVars: function(targetVariables, options) { var result = {}; if (!targetVariables) targetVariables = this.getLocalVarSymbols(); for (var i = 0, l = targetVariables.length; i < l; ++i) { var symbol = this._varToVarSymbol(targetVariables[i]); result[symbol] = this.getVarDisplayRange(targetVariables[i], options); } return result; }, /** @private */ _varToVarSymbol: function(targetVar) { /* var info = this.getLocalVarInfo(targetVar); if (info) return info.varDef.getSymbol(); */ var varDef = this.getLocalVarDef(targetVar); if (varDef) return varDef.getSymbol(); else return null; }, /** @private */ _varToVarSymbols: function(targetVariables) { var targetVarSymbols = []; var vars = targetVariables? AU.toArray(targetVariables): null; if (!vars) targetVarSymbols = this.getLocalVarSymbols(); else { for (var i = 0, l = vars.length; i < l; ++i) { targetVarSymbols.push(this._varToVarSymbol(vars[i])) } } return targetVarSymbols; }, /** @private */ _getDefaultPeakRoot: function() { var result = {}; var varInfos = this.getActualLocalVarInfos(); for (var i = 0, l = varInfos.length; i < l; ++i) { //var varDef = varInfos[i].varDef; var varDef = this.getLocalVarDef(i); if (varDef.getDependency() !== Kekule.VarDependency.INDEPENDENT) { result[varDef.getSymbol()] = 0; } } return result; }, /** * Iterate all data items and calculate the min/max value of each variable. * Note this function will always returns the value based on internal unit, * regardless of whether the external unit is set or not. * @param {Array} targetVariables Array of variable definition or symbol. * If not set, all variables will be calculated. * @param {Hash} options Extra calculation options, may include fields: * { * basedOnInternalUnit: Bool. If true, the returned value will be based on internal unit rather than the external unit of variable. * ignorePeakRoot: Bool. If true, the peak root value will be ignored during calculation. * } * @returns {Hash} */ calcDataRange: function(targetVariables, options) { var op = options || {}; // since calculation of data range is a time-consuming job, here we cache the result var targetVarSymbols = this._varToVarSymbols(targetVariables); var notNum = function (v) { return !Kekule.NumUtils.isNormalNumber(v); }; var ranges = {}; var rangeCache = this._cache.ranges; if (!rangeCache) { rangeCache = {}; this._cache.ranges = rangeCache; } var remainingVarSymbols = []; for (var i = 0, l = targetVarSymbols.length; i < l; ++i) { var symbol = targetVarSymbols[i]; if (rangeCache[symbol]) // cached { // console.log('got range from cache', symbol); ranges[symbol] = Object.extend({}, rangeCache[symbol]); } else remainingVarSymbols.push(symbol); } if (remainingVarSymbols.length) { var self = this; var isPeakData = this.isPeakSection(); this.forEach(function (dataValue, index) { for (var i = 0, l = remainingVarSymbols.length; i < l; ++i) { var symbol = remainingVarSymbols[i]; if (notNum(dataValue[symbol])) continue; if (!ranges[symbol]) ranges[symbol] = {}; ranges[symbol].min = notNum(ranges[symbol].min) ? dataValue[symbol] : Math.min(ranges[symbol].min, dataValue[symbol]); ranges[symbol].max = notNum(ranges[symbol].max) ? dataValue[symbol] : Math.max(ranges[symbol].max, dataValue[symbol]); // consider peak root value if (isPeakData && !op.ignorePeakRoot) { var peakRootValue = self.getPeakRootValueOf(dataValue); if (peakRootValue && !notNum(peakRootValue[symbol])) { ranges[symbol].min = notNum(ranges[symbol].min) ? peakRootValue[symbol] : Math.min(ranges[symbol].min, peakRootValue[symbol]); ranges[symbol].max = notNum(ranges[symbol].max) ? peakRootValue[symbol] : Math.max(ranges[symbol].max, peakRootValue[symbol]); } } } }, null, {basedOnInternalUnit: true}); // here we use the internal unit, to keep the cache with the same unit // cache the range values for (var i = 0, l = remainingVarSymbols.length; i < l; ++i) { var symbol = remainingVarSymbols[i]; rangeCache[symbol] = Object.extend({}, ranges[symbol]); } } /* if (this.getMode() === Kekule.Spectroscopy.DataMode.PEAK) // consider the peak root { var peakRoot = this.getDefPeakRoot() || this._getDefaultPeakRoot(); for (var i = 0, l = targetVarSymbols.length; i < l; ++i) { var symbol = targetVarSymbols[i]; var rootValue = peakRoot[symbol]; if (!notNum(rootValue)) { ranges[symbol].min = Math.min(ranges[symbol].min, rootValue); ranges[symbol].max = Math.max(ranges[symbol].max, rootValue); } } } */ //console.log(this.getMode(), peakRoot, ranges); if (!op.basedOnInternalUnit) { for (var i = 0, l = targetVarSymbols.length; i < l; ++i) { var symbol = targetVarSymbols[i]; ranges[symbol] = this._convertDataRangeToExternalUnit(ranges[symbol], i); } } return ranges; }, /** @private */ _convertDataRangeToExternalUnit: function(range, varIndex) { if (!range) return range; var fieldNames = ['min', 'max']; for (var i = 0, l = fieldNames.length; i < l; ++i) { var fname = fieldNames[i]; range[fname] = this._convertVarValueToExternal(range[fname], varIndex); } // after conversion, the min/max values may be reversed if (range.min > range.max) { var temp = range.min; range.min = range.max; range.max = temp; } return range; }, /** @private */ _convertDataRangeToInternalUnit: function(range, varIndex) { if (!range) return range; var fieldNames = ['min', 'max']; for (var i = 0, l = fieldNames.length; i < l; ++i) { var fname = fieldNames[i]; range[fname] = this._convertVarValueToInternal(range[fname], varIndex); } // after conversion, the min/max values may be reversed if (range.min > range.max) { var temp = range.min; range.min = range.max; range.max = temp; } return range; }, /** * Iterate all data items and calculate the average value of each variable. * Note this function will always returns the value based on internal unit, * regardless of whether the external unit is set or not. * @param {Array} targetVariables Array of variable definition or symbol. * If not set, all variables will be calculated. * @param {Hash} options Extra calculation options, may include fields: * { * basedOnInternalUnit: Bool. If true, the returned value will be based on internal unit rather than the external unit of variable. * } * @returns {Hash} */ calcDataAverage: function(targetVariables, options) { var op = options || {}; var targetVarSymbols = this._varToVarSymbols(targetVariables); var averages = {}; var averageCache = this._cache.averages; var notNum = function (v) { return !Kekule.NumUtils.isNormalNumber(v); }; if (!averageCache) { averageCache = {}; this._cache.averages = averageCache; } var remainingVarSymbols = []; for (var i = 0, l = targetVarSymbols.length; i < l; ++i) { var symbol = targetVarSymbols[i]; if (!notNum(averageCache[symbol])) // cached { averages[symbol] = averageCache[symbol]; } else remainingVarSymbols.push(symbol); } if (remainingVarSymbols.length) { var sums = {}; var counts = {}; for (var i = 0, l = remainingVarSymbols.length; i < l; ++i) { sums[remainingVarSymbols[i]] = 0; counts[remainingVarSymbols[i]] = 0; } this.forEach(function (dataValue, index) { for (var i = 0, l = remainingVarSymbols.length; i < l; ++i) { var symbol = remainingVarSymbols[i]; var value = dataValue[symbol]; if (notNum(value)) continue; sums[symbol] += value; ++counts[symbol]; } }, null, {basedOnInternalUnit: true}); // cache the average values for (var i = 0, l = remainingVarSymbols.length; i < l; ++i) { var symbol = remainingVarSymbols[i]; averages[symbol] = sums[symbol] / counts[symbol]; averageCache[symbol] = averages[symbol]; } } if (!op.basedOnInternalUnit) { for (var i = 0, l = targetVarSymbols.length; i < l; ++i) { var symbol = targetVarSymbols[i] averages[symbol] = this._convertVarValueToExternal(averages[symbol, i]); } } return averages; }, /** * Returns the symbols of continuous variable. * @returns {Array} */ getContinuousVarSymbols: function() { var result = []; var varInfos = this.getActualLocalVarInfos(); for (var i = 0, l = varInfos.length; i < l; ++i) { if (this.getContinuousVarRange(i)) result.push(varInfos[i].symbol); } return result; }, /** @private */ _itemHashToArray: function(hashValue) { if (!hashValue) return null; var result = []; var symbols = this.getLocalVarSymbols(); for (var i = 0, l = symbols.length; i < l; ++i) { result.push(hashValue[symbols[i]]); } var src = this._getDataValueSrc(hashValue); if (src) { this._setSys