kekule
Version:
Open source JavaScript toolkit for chemoinformatics
1,579 lines (1,529 loc) • 146 kB
JavaScript
(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