kekule
Version:
Open source JavaScript toolkit for chemoinformatics
1,126 lines (1,060 loc) • 108 kB
JavaScript
/**
* @fileoverview
* Renderer for spectrum objects.
* @author Partridge Jiang
*/
/*
* requires /core/kekule.common.js
* requires /utils/kekule.utils.js
* requires /render/kekule.render.base.js
* requires /render/kekule.render.utils.js
* requires /render/kekule.baseTextRender.js
* requires /spectroscopy/kekule.spectrum.core.js
* requires /localization/
*/
(function(){
"use strict";
Kekule._registerAfterLoadSysProc(function(){
// the following codes will be run after both render and spectroscpy module are loaded
if (!Kekule.Render || !Kekule.Render.ChemObj2DRenderer)
return;
Kekule.globalOptions.add('render.spectrum', {
continuousSpectrumResampleRatio: 1
});
var CU = Kekule.CoordUtils;
var AU = Kekule.ArrayUtils;
/**
* Options to display a spectrum.
* @class
* @augments Kekule.AbstractConfigs
*
* @property {Hash} defSize2DRatio Default 2D spectrum size({x, y}), these value multiply the default ref length will get the actual size.
* @property {Bool} reversedAxises Whether reverse the X/Y axis and rotate the spectrum with 90 degree.
* //@property {Int} spectrumIndicatorElements Default displayed indicator elements in spectrum.
* @property {Bool} reverseIndependentDataDirection Whether reverse the min->max direction of independent data from the usual convention.
* @property {Bool} reverseDependentDataDirection Whether reverse the min->max direction of dependent data from the usual convention.
* @property {Bool} reverseIndependentAxisAlign Whether reverse alignment of independent data axis from the usual convention.
* @property {Bool} reverseDependentAxisAlign Whether reverse alignment of dependent data axis from the usual convention.
*
* @property {String} dataColor Color to draw the spectrum data curve.
* @property {Number} dataStrokeWidthRatio The actual stroke width of data curve is calculated from max(dataStrokeWidthRatio * refLength, dataStrokeWidthMin).
* @property {Number} dataStrokeWidthMin The actual stroke width of data curve is calculated from max(dataStrokeWidthRatio * refLength, dataStrokeWidthMin).
*
* @property {Bool} displayIndependentAxis Whether rendering the independent data axis.
* @property {Bool} displayIndependentAxisScales Whether rendering the scales in independent data axis.
* @property {Bool} displayIndependentAxisLabel Whether rendering the axis label of independent data.
* @property {Bool} displayIndependentAxisUnit Whether rendering the unit of independent data.
* @property {Bool} displayDependentAxis Whether rendering the dependent data axis.
* @property {Bool} displayDependentAxisScales Whether rendering the scales in dependent data axis.
* @property {Bool} displayDependentAxisLabel Whether rendering the axis label of dependent data.
* @property {Bool} displayDependentAxisUnit Whether rendering the unit of dependent data.
* @property {Bool} displaySpectrum Whether rendering the spectrum curve.
*
* @property {String} axisScaleLabelFontFamily Font family to render axis scale labels.
* @property {Number} axisScaleLabelFontSize Font size to render axis scale labels.
* @property {String} axisScaleLabelColor Color to render axis scale labels.
* @property {String} axisLabelFontFamily Font family to render axis label.
* @property {Number} axisLabelFontSize Font size to render axis label.
* @property {String} axisLabelColor Color to render axis label.
*
* @property {Color} axisColor Color to render data axis.
* @property {Number} axisWidthRatio The actual stroke width of data axis is calculated from max(axisWidthRatio * refLength, axisWidthMin).
* @property {Number} axisWidthMin The actual stroke width of data axis is calculated from max(axisWidthRatio * refLength, axisWidthMin).
* @property {Number} axisScaleMarkSizeRatio The actual size of scale marks in axis is calculated from max(axisScaleMarkSizeRatio * refLength, axisScaleMarkSizeMin).
* @property {Number} axisScaleMarkSizeMin The actual size of scale marks in axis is calculated from max(axisScaleMarkSizeRatio * refLength, axisScaleMarkSizeMin).
* @property {Number} axisUnlabeledScaleSizeRatio The size of scale mark without a label is calculated from max(axisUnlabeledScaleSizeRatio * axisScaleMarkSizeRatio * refLength, axisScaleMarkSizeMin).
* @property {Number} axisLabelPaddingRatio The padding of axis label is calculated from axisLabelPaddingRatio * refLength.
* @property {Number} axisScaleLabelPaddingRatio The padding of axis scale labels is calculated from axisLabelPaddingRatio * refLength.
*
* @property {Number} axisScaleMarkPreferredCount Preferred scale count in data axis.
*/
Kekule.Render.SpectrumDisplayConfigs = Class.create(Kekule.AbstractConfigs,
/** @lends Kekule.Render.SpectrumDisplayConfigs# */
{
/** @private */
CLASS_NAME: 'Kekule.Render.SpectrumDisplayConfigs',
/** @private */
initProperties: function()
{
this.addHashConfigProp('defSize2DRatio');
//this.addHashConfigProp('defSpectrumSize3D');
//this.addBoolConfigProp('spectrumAbscissaAxisOnMinEnd', true);
this.addBoolConfigProp('reversedAxises', false);
this.addBoolConfigProp('reverseIndependentDataDirection', false);
this.addBoolConfigProp('reverseDependentDataDirection', false);
this.addBoolConfigProp('reverseIndependentAxisAlign', false);
this.addBoolConfigProp('reverseDependentAxisAlign', false);
//this.addBoolConfigProp('spectrumAbscissaAxisOnMinEnd', true);
//this.addBoolConfigProp('spectrumOrdinateAxisOnMinEnd', true);
this.addStrConfigProp('dataColor', '#000000');
this.addFloatConfigProp('dataStrokeWidthRatio', 0.025);
this.addFloatConfigProp('dataStrokeWidthMin', 1);
this.addFloatConfigProp('dataStrokeWidthMax', undefined);
this.addFloatConfigProp('dataStrokeWidthFixed', undefined);
// default data range
this.addFloatConfigProp('visibleIndependentDataRangeFrom', 0);
this.addFloatConfigProp('visibleIndependentDataRangeTo', 1);
this.addFloatConfigProp('visibleDependentDataRangeFrom', -0.05);
this.addFloatConfigProp('visibleDependentDataRangeTo', 1.05);
// specified data range for peak/continuous spectrum
this.addFloatConfigProp('visibleIndependentDataRangeFrom_Peak', -0.05);
this.addFloatConfigProp('visibleIndependentDataRangeTo_Peak', 1.05);
this.addFloatConfigProp('visibleDependentDataRangeFrom_Peak', -0.05);
this.addFloatConfigProp('visibleDependentDataRangeTo_Peak', 1.05);
this.addFloatConfigProp('visibleIndependentDataRangeFrom_Continuous', 0);
this.addFloatConfigProp('visibleIndependentDataRangeTo_Continuous', 1);
this.addFloatConfigProp('visibleDependentDataRangeFrom_Continuous', -0.05);
this.addFloatConfigProp('visibleDependentDataRangeTo_Continuous', 1.05);
// config about displayed elements
//this.addBoolConfigProp('displaySpectrumGrid', true);
this.addBoolConfigProp('displayIndependentAxis', true);
this.addBoolConfigProp('displayIndependentAxisScales', true);
this.addBoolConfigProp('displayIndependentAxisLabel', true);
this.addBoolConfigProp('displayIndependentAxisUnit', true);
this.addBoolConfigProp('displayDependentAxis', true);
this.addBoolConfigProp('displayDependentAxisScales', true);
this.addBoolConfigProp('displayDependentAxisLabel', true);
this.addBoolConfigProp('displayDependentAxisUnit', true);
this.addBoolConfigProp('displaySpectrum', true);
// configs of elements
this.addStrConfigProp('axisScaleLabelFontFamily', 'Arial, Helvetica, sans-serif');
this.addFloatConfigProp('axisScaleLabelFontSize', 7);
this.addStrConfigProp('axisScaleLabelColor', '#000000');
this.addStrConfigProp('axisLabelFontFamily', 'Arial, Helvetica, sans-serif');
this.addFloatConfigProp('axisLabelFontSize', 10);
this.addStrConfigProp('axisLabelColor', '#000000');
this.addStrConfigProp('axisColor', '#000000');
this.addFloatConfigProp('axisWidthRatio', 0.025);
this.addFloatConfigProp('axisWidthMin', 1);
this.addFloatConfigProp('axisWidthMax', undefined);
this.addFloatConfigProp('axisWidthFixed', undefined);
this.addFloatConfigProp('axisScaleMarkSizeRatio', 0.1);
this.addFloatConfigProp('axisScaleMarkSizeMin', 3);
this.addFloatConfigProp('axisScaleMarkSizeMax', undefined);
this.addFloatConfigProp('axisScaleMarkSizeFixed', undefined);
this.addFloatConfigProp('axisUnlabeledScaleSizeRatio', 0.7);
this.addIntConfigProp('axisScaleMarkPreferredCount', 10);
this.addFloatConfigProp('axisLabelPaddingRatio', 0.02);
this.addFloatConfigProp('axisScaleLabelPaddingRatio', 0.02);
},
/** @ignore */
initPropDefValues: function()
{
this.tryApplySuper('initPropDefValues');
//var defScaleRefLength = 0.8; // Kekule.Render.getRender2DConfigs().getLengthConfigs().getDefScaleRefLength();
this.setDefSize2DRatio({'x': 12, 'y': 8});
//this.setDefSpectrumSize3D({'x': defScaleRefLength * 10, 'y': defScaleRefLength * 10, 'z': defScaleRefLength * 10});
},
/** @ignore */
doGetPropNameToHashPrefix: function()
{
return 'spectrum_';
}
});
// extents some render config classes
/** @ignore */
ClassEx.extendMethod(Kekule.Render.Render2DConfigs, 'initProperties',
function(originMethod)
{
originMethod();
this.addConfigProp('spectrumDisplayConfigs', 'Kekule.Render.SpectrumDisplayConfigs');
});
/** @ignore */
ClassEx.extendMethod(Kekule.Render.Render2DConfigs, 'initPropDefValues',
function(originMethod)
{
originMethod();
this.setPropStoreFieldValue('spectrumDisplayConfigs', new Kekule.Render.SpectrumDisplayConfigs());
});
// extents Spectrum class to init with the default size
ClassEx.extendMethod(Kekule.Spectroscopy.Spectrum, 'initPropValues',
function(originMethod)
{
originMethod();
var size2D = this.getSize2D();
if (!size2D) // initial a default 2D size of spectrum
{
var configs = Kekule.Render.Render2DConfigs.getInstance();
var sizeRatio = configs.getSpectrumDisplayConfigs().getDefSize2DRatio();
var refLength = configs.getLengthConfigs().getDefScaleRefLength();
if (sizeRatio && refLength)
size2D = CU.multiply(sizeRatio, refLength);
this.setSize2D(size2D);
}
});
ClassEx.extendMethod(Kekule.Spectroscopy.Spectrum, 'doGetObjAnchorPosition',
function(originMethod, coordMode)
{
return Kekule.ObjAnchorPosition.CENTER;
});
ClassEx.extend(Kekule.Spectroscopy.Spectrum, {
getDisplayedDataSections: function()
{
return [this.getActiveDataSection()];
},
getPseudoRenderSubObject: function(objName, doNotCreate)
{
var subObjs = this.__$subRenderObjs$__;
if (!subObjs)
{
subObjs = {};
this.__$subRenderObjs$__ = subObjs;
}
var result = subObjs[objName];
if (!result && !doNotCreate)
{
result = {'parent': this};
subObjs[objName] = result;
}
return result;
},
isChildPseudoRenderSubObject: function(obj)
{
return this.__$subRenderObjs$__ && (this.__$subRenderObjs$__.indexOf(obj) >= 0);
},
getDataItemOverridenRenderOptions: function(dataItem)
{
for (var i = 0, l = this.getDataSectionCount(); i < l; ++i)
{
var sec = this.getDataSectionAt(i);
var index = sec.indexOfDataItem(dataItem);
if (index >= 0)
return sec.getDataItemOverridenRenderOptions(dataItem);
}
return null;
},
setDataItemRenderOptions: function(dataItem, options)
{
for (var i = 0, l = this.getDataSectionCount(); i < l; ++i)
{
var sec = this.getDataSectionAt(i);
var index = sec.indexOfDataItem(dataItem);
if (index >= 0)
sec.setDataItemRenderOptions(dataItem, options);
}
},
setDataItemOverrideRenderOptions: function(dataItem, options)
{
for (var i = 0, l = this.getDataSectionCount(); i < l; ++i)
{
var sec = this.getDataSectionAt(i);
var index = sec.indexOfDataItem(dataItem);
if (index >= 0)
sec.setDataItemOverrideRenderOptions(dataItem, options);
}
},
addDataItemOverrideRenderOptionItem: function(dataItem, optionItem)
{
for (var i = 0, l = this.getDataSectionCount(); i < l; ++i)
{
var sec = this.getDataSectionAt(i);
var index = sec.indexOfDataItem(dataItem);
if (index >= 0)
{
sec.addDataItemOverrideRenderOptionItem(dataItem, optionItem);
//console.log('add', dataItem, this.getDataItemOverridenRenderOptions(dataItem));
}
}
},
removeDataItemOverrideRenderOptionItem: function(dataItem, optionItem)
{
for (var i = 0, l = this.getDataSectionCount(); i < l; ++i)
{
var sec = this.getDataSectionAt(i);
var index = sec.indexOfDataItem(dataItem);
if (index >= 0)
{
sec.removeDataItemOverrideRenderOptionItem(dataItem, optionItem);
//console.log('remove', dataItem, this.getDataItemOverridenRenderOptions(dataItem));
}
}
}
});
ClassEx.extend(Kekule.Spectroscopy.SpectrumDataSection, {
_getDataItemSpecifiedRenderOptions: function(dataItem, propName)
{
var extra = this.getExtraInfoOf(dataItem);
if (!extra) // try create new one
{
extra = this.createDefaultExtraInfoObjectFor(dataItem);
}
if (extra)
{
if ((extra instanceof ObjectEx) && (extra.hasProperty(propName)))
{
return extra.getPropValue(propName);
}
else
{
return extra[propName];
}
}
},
_setDataItemSpecifiedRenderOptions: function(dataItem, propName, options)
{
var extra = this.getExtraInfoOf(dataItem);
if (!extra) // try create new one
{
extra = this.createDefaultExtraInfoObjectFor(dataItem);
}
if (extra)
{
if ((extra instanceof ObjectEx) && (extra.hasProperty(propName)))
{
extra.setPropValue(propName, options);
}
else
{
extra[propName] = options;
}
}
},
getDataItemRenderOptions: function(dataItem)
{
return this._getDataItemSpecifiedRenderOptions(dataItem, 'renderOptions');
},
setDataItemRenderOptions: function(dataItem, options)
{
return this._setDataItemSpecifiedRenderOptions(dataItem, 'renderOptions', options);
},
getDataItemOverrideRenderOptionItems: function(dataItem)
{
return this._getDataItemSpecifiedRenderOptions(dataItem, 'overrideRenderOptionItems');
},
setDataItemOverrideRenderOptionItems: function(dataItem, options)
{
return this._setDataItemSpecifiedRenderOptions(dataItem, 'overrideRenderOptionItems', options);
},
addDataItemOverrideRenderOptionItem: function(dataItem, optionItem)
{
var extra = this.getExtraInfoOf(dataItem);
if (!extra) // try create new one
{
extra = this.createDefaultExtraInfoObjectFor(dataItem);
}
if (extra)
{
if (extra.addOverrideRenderOptionItem)
{
extra.addOverrideRenderOptionItem(optionItem);
}
else
{
var roItems = this.getDataItemOverrideRenderOptionItems();
if (!roItems)
{
roItems = [optionItem];
this.setDataItemOverrideRenderOptionItems(dataItem, roItems);
}
else
{
var oldIndex = roItems.indexOf(optionItem);
if (oldIndex >= 0)
roItems.splice(oldIndex, 1);
roItems.push(optionItem);
}
}
}
},
removeDataItemOverrideRenderOptionItem: function(dataItem, optionItem)
{
var extra = this.getExtraInfoOf(dataItem);
if (!extra) // try create new one
{
return;
}
else
{
if (extra.removeOverrideRenderOptionItem)
extra.removeOverrideRenderOptionItem(optionItem);
else
{
var roItems = this.getDataItemOverrideRenderOptionItems();
if (roItems)
{
var oldIndex = roItems.indexOf(optionItem);
if (oldIndex >= 0)
roItems.splice(oldIndex, 1);
}
}
}
},
getDataItemOverridenRenderOptions: function(dataItem)
{
var extra = this.getExtraInfoOf(dataItem);
if (!extra)
return null;
else
{
if (extra.getOverriddenRenderOptions) // extra is instance of ChemObject
return extra.getOverriddenRenderOptions();
else // raw object?
{
var renderOptions = extra.renderOptions || {};
var result = Object.extend({}, renderOptions);
var overrideOptionItems = extra.overrideRenderOptionItems || [];
for (var i = 0, l = overrideOptionItems.length; i < l; ++i)
{
result = Object.extend(result, overrideOptionItems[i]);
}
return result;
}
}
}
});
ClassEx.extendMethod(Kekule.Spectroscopy.SpectrumDataSection, '_extractAllExtraInfoOfDataItems', function($origin){
// remove the overrideRenderOptions of extra infos, avoid serializing it
var oldResult = $origin() || [];
var result = [];
for (var i = 0, l = oldResult.length; i < l; ++i)
{
var info = oldResult[i];
if (info && !(info instanceof Kekule.ChemObject) && (DataType.isObjectValue(info)) && info.overrideRenderOptions)
{
var dupInfo = Object.create(info);
dupInfo.overrideRenderOptions = undefined;
result.push(dupInfo);
}
else
result.push(info);
}
return result;
});
// extend Kekule.Render.RenderOptionUtils.getOptionDefinitions for property editors for spectrum objects
/** @ignore */
function extendRenderOptionPropEditors()
{
var appendDefinitionItem = function(definitions, fieldName, dataType, targetClasses, fieldNamePrefixes)
{
var classes = AU.toArray(targetClasses);
var prefixes = fieldNamePrefixes || [''];
for (var i = 0, l = classes.length; i < l; ++i)
{
for (var j = 0, k = prefixes.length; j < k; ++j)
{
var name = prefixes[j]? (prefixes[j] + fieldName.upperFirst()): fieldName;
definitions.push({'name': 'spectrum_' + name, 'dataType': dataType, 'targetClass': classes[i]});
}
}
};
//Kekule.Render.RenderOptionUtils.getOptionDefinitions = function()
var getSpectrumObjRenderOptionFieldList = function()
{
//var result = Kekule.Render.RenderOptionUtils.getOptionDefinitions();
var result = [];
var SpectrumClass = Kekule.Spectroscopy.Spectrum;
var SpectrumDataSectionClass = Kekule.Spectroscopy.SpectrumDataSection;
var axisPrefixes = ['', 'independent', 'dependent'];
appendDefinitionItem(result, 'reversedAxises', DataType.BOOL, SpectrumClass);
appendDefinitionItem(result, 'reverseIndependentDataDirection', DataType.BOOL, SpectrumClass);
appendDefinitionItem(result, 'reverseDependentDataDirection', DataType.BOOL, SpectrumClass);
appendDefinitionItem(result, 'reverseIndependentAxisAlign', DataType.BOOL, SpectrumClass);
appendDefinitionItem(result, 'reverseDependentAxisAlign', DataType.BOOL, SpectrumClass);
appendDefinitionItem(result, 'dataColor', DataType.STRING, [SpectrumClass, SpectrumDataSectionClass]);
appendDefinitionItem(result, 'dataStrokeWidthRatio', DataType.FLOAT, [SpectrumClass, SpectrumDataSectionClass]);
appendDefinitionItem(result, 'dataStrokeWidthMin', DataType.NUMBER, [SpectrumClass, SpectrumDataSectionClass]);
appendDefinitionItem(result, 'dataStrokeWidthMax', DataType.NUMBER, [SpectrumClass, SpectrumDataSectionClass]);
appendDefinitionItem(result, 'dataStrokeWidthFixed', DataType.NUMBER, [SpectrumClass, SpectrumDataSectionClass]);
appendDefinitionItem(result, 'visibleIndependentDataRangeFrom_Continuous', DataType.FLOAT, SpectrumClass);
appendDefinitionItem(result, 'visibleIndependentDataRangeTo_Continuous', DataType.FLOAT, SpectrumClass);
appendDefinitionItem(result, 'visibleDependentDataRangeFrom_Continuous', DataType.FLOAT, SpectrumClass);
appendDefinitionItem(result, 'visibleDependentDataRangeTo_Continuous', DataType.FLOAT, SpectrumClass);
appendDefinitionItem(result, 'visibleIndependentDataRangeFrom_Peak', DataType.FLOAT, SpectrumClass);
appendDefinitionItem(result, 'visibleIndependentDataRangeTo_Peak', DataType.FLOAT, SpectrumClass);
appendDefinitionItem(result, 'visibleDependentDataRangeFrom_Peak', DataType.FLOAT, SpectrumClass);
appendDefinitionItem(result, 'visibleDependentDataRangeTo_Peak', DataType.FLOAT, SpectrumClass);
appendDefinitionItem(result, 'displayIndependentAxis', DataType.BOOL, SpectrumClass);
appendDefinitionItem(result, 'displayIndependentAxisScales', DataType.BOOL, SpectrumClass);
appendDefinitionItem(result, 'displayIndependentAxisLabel', DataType.BOOL, SpectrumClass);
appendDefinitionItem(result, 'displayIndependentAxisUnit', DataType.BOOL, SpectrumClass);
appendDefinitionItem(result, 'displayDependentAxis', DataType.BOOL, SpectrumClass);
appendDefinitionItem(result, 'displayDependentAxisScales', DataType.BOOL, SpectrumClass);
appendDefinitionItem(result, 'displayDependentAxisLabel', DataType.BOOL, SpectrumClass);
appendDefinitionItem(result, 'displayDependentAxisUnit', DataType.BOOL, SpectrumClass);
appendDefinitionItem(result, 'displaySpectrum', DataType.BOOL, SpectrumClass);
appendDefinitionItem(result, 'axisScaleLabelFontFamily', DataType.STRING, SpectrumClass, axisPrefixes);
appendDefinitionItem(result, 'axisScaleLabelFontSize', DataType.NUMBER, SpectrumClass, axisPrefixes);
appendDefinitionItem(result, 'axisScaleLabelColor', DataType.STRING, SpectrumClass, axisPrefixes);
appendDefinitionItem(result, 'axisLabelFontFamily', DataType.STRING, SpectrumClass, axisPrefixes);
appendDefinitionItem(result, 'axisLabelFontSize', DataType.NUMBER, SpectrumClass, axisPrefixes);
appendDefinitionItem(result, 'axisLabelColor', DataType.STRING, SpectrumClass, axisPrefixes);
appendDefinitionItem(result, 'axisColor', DataType.STRING, SpectrumClass, axisPrefixes);
appendDefinitionItem(result, 'axisWidthRatio', DataType.FLOAT, SpectrumClass, axisPrefixes);
appendDefinitionItem(result, 'axisWidthMin', DataType.NUMBER, SpectrumClass, axisPrefixes);
appendDefinitionItem(result, 'axisWidthMax', DataType.NUMBER, SpectrumClass, axisPrefixes);
appendDefinitionItem(result, 'axisWidthFixed', DataType.NUMBER, SpectrumClass, axisPrefixes);
appendDefinitionItem(result, 'axisScaleMarkSizeRatio', DataType.FLOAT, SpectrumClass, axisPrefixes);
appendDefinitionItem(result, 'axisScaleMarkSizeMin', DataType.NUMBER, SpectrumClass, axisPrefixes);
appendDefinitionItem(result, 'axisScaleMarkSizeMax', DataType.NUMBER, SpectrumClass, axisPrefixes);
appendDefinitionItem(result, 'axisScaleMarkSizeFixed', DataType.NUMBER, SpectrumClass, axisPrefixes);
appendDefinitionItem(result, 'axisUnlabeledScaleSizeRatio', DataType.FLOAT, SpectrumClass, axisPrefixes);
appendDefinitionItem(result, 'axisScaleMarkPreferredCount', DataType.NUMBER, SpectrumClass, axisPrefixes);
appendDefinitionItem(result, 'axisLabelPaddingRatio', DataType.FLOAT, SpectrumClass, axisPrefixes);
appendDefinitionItem(result, 'axisScaleLabelPaddingRatio', DataType.NUMBER, SpectrumClass, axisPrefixes);
return result;
}
// hack
if (Kekule.PropertyEditor && Kekule.PropertyEditor.ChemRender2DOptionsEditor)
{
var proto = ClassEx.getPrototype(Kekule.PropertyEditor.ChemRender2DOptionsEditor);
proto.CHILD_FIELD_INFOS = proto.CHILD_FIELD_INFOS.concat(getSpectrumObjRenderOptionFieldList());
}
};
Kekule._registerAfterLoadSysProc(extendRenderOptionPropEditors);
/**
* Helper Util class to render coordinate axises in spectrum.
* @class
*/
Kekule.Render.CoordAxisRender2DUtils = {
/**
* Draw abscissa and ordinate axis.
* @param {Object} drawBridge
* @param {Object} richTextDrawer
* @param {Object} context
* @param {Hash} renderBox
* @param {Hash} params Params to draw axises.
* It may contain the following fields:
* {
* abscissaDataRange: hash of {min, max},
* abscissaScales: array of scale numbers,
* abscissaScaleBase: scale based number, e.g. 10e3
* abscissaScaleUseSciForm: bool,
* abscissaScaleFixedDigitCountAfterPoint: int,
* //abscissaScaleMarkSize,
* abscissaUnitLabel: rich text label for abscissa axis unit.
* abscissaLabel: rich text label for abscissa axis,
* abscissaAxisPosition: int, 0 for on bottom and 1 for on top
* abscissaReversedDirection: bool, set true to draw min value on right and max on left
* ordinateDataRange: hash of {min, max},
* ordinateScales: array of scale numbers,
* ordinateScaleBase: scale based number, e.g. 10e3
* ordinateScaleUseSciForm: bool,
* ordinateScaleFixedDigitCountAfterPoint: int,
* //ordinateScaleMarkSize,
* ordinateUnitLabel: rich text label for ordinate axis unit.
* ordinateLabel: rich text label for ordinate axis
* ordinateAxisPosition: int, 0 for on left and 1 for on right
* ordinateReversedDirection: bool, set true to draw min value on top and max on bottom
* }
* @param {Hash} renderOptions
* @Returns {Hash} A hash object containing fields: {drawnElem, clientBox}
*/
drawAxises: function(drawBridge, richTextDrawer, context, renderBox, params, renderOptions)
{
//console.log('drawAxises', renderBox, params, renderOptions);
var getScaleRichTexts = function(scales, fixDigitsCountAfterPoint, scaleBase, useSciForm)
{
var RU = Kekule.Render.RichTextUtils;
var result = [];
var sciFormExponent = useSciForm? Math.round(Math.log10(scaleBase)): null;
//var digitCountAfterPoint = (scaleBase > 1)? 0:
for (var i = 0, l = scales.length; i < l; ++i)
{
var scale = scales[i];
//var digitCount = Math.log10(Math.abs(scale));
//var useSciForm = digitCount > 6; // TODO: Currently fixed
var num = sciFormExponent? scale / scaleBase: scale;
var snum = num.toFixed(fixDigitsCountAfterPoint);
var rt;
if (!sciFormExponent || Kekule.NumUtils.isFloatEqual(snum, 0)) // normal form
rt = RU.strToRichText(snum);
else
{
rt = RU.createGroup(null, {'charDirection': Kekule.Render.TextDirection.LTR});
var coreSec = RU.appendText2(rt, snum + '×10');
RU.appendText(rt, sciFormExponent.toFixed(0), {'textType': Kekule.Render.RichText.SUP, 'refItem': coreSec});
}
result.push(rt);
}
return result;
};
var drawAbscissa = !!params.abscissaDataRange;
var drawOrdinate = !!params.ordinateDataRange;
if (!drawAbscissa && !drawOrdinate)
return null;
var abscissaScaleLabels = (drawAbscissa && params.abscissaScales)?
getScaleRichTexts(params.abscissaScales, params.abscissaScaleFixedDigitCountAfterPoint, params.abscissaScaleBase, params.abscissaScaleUseSciForm): null;
var ordinateScaleLabels = (drawOrdinate && params.ordinateScales)?
getScaleRichTexts(params.ordinateScales, params.ordinateScaleFixedDigitCountAfterPoint, params.ordinateScaleBase, params.ordinateScaleUseSciForm): null;
// TODO: currently we always draw axis label and unit together
var RTU = Kekule.Render.RichTextUtils;
var actualParams = Object.create(params);
if (drawAbscissa)
{
if (actualParams.abscissaLabel && actualParams.abscissaUnitLabel) // merge two labels
{
var newLabel = Kekule.Render.RichTextUtils.createGroup(null, {'charDirection': Kekule.Render.TextDirection.LTR});
RTU.append(newLabel, actualParams.abscissaLabel);
RTU.appendText(newLabel, ' (');
RTU.append(newLabel, actualParams.abscissaUnitLabel);
RTU.appendText(newLabel, ')');
actualParams.abscissaLabel = newLabel;
}
else
actualParams.abscissaLabel = actualParams.abscissaLabel || actualParams.abscissaUnitLabel;
actualParams.abscissaUnitLabel = null;
}
if (drawOrdinate)
{
if (actualParams.ordinateLabel && actualParams.ordinateUnitLabel) // merge two labels
{
var newLabel = Kekule.Render.RichTextUtils.createGroup(null, {'charDirection': Kekule.Render.TextDirection.LTR});
RTU.append(newLabel, actualParams.ordinateLabel);
RTU.appendText(newLabel, ' (');
RTU.append(newLabel, actualParams.ordinateUnitLabel);
RTU.appendText(newLabel, ')');
actualParams.ordinateLabel = newLabel;
}
else
actualParams.ordinateLabel = actualParams.ordinateLabel || actualParams.ordinateUnitLabel;
actualParams.ordinateUnitLabel = null;
}
// estimate the size of axis
var abscissaSizes = drawAbscissa?
ARU._estimateAxisSizes(drawBridge, richTextDrawer, context, renderBox, abscissaScaleLabels, actualParams.abscissaUnitLabel, actualParams.abscissaLabel, renderOptions, true, actualParams.abscissaAxisPosition === 1):
null;
var ordinateSizes = drawOrdinate?
ARU._estimateAxisSizes(drawBridge, richTextDrawer, context, renderBox, ordinateScaleLabels, actualParams.ordinateUnitLabel, actualParams.ordinateLabel, renderOptions, false, actualParams.ordinateAxisPosition === 0):
null;
// do the concrete drawing
var result = drawBridge.createGroup(context);
var elem;
var abscissaRenderBox = Object.create(renderBox);
var ordinateRenderBox = Object.create(renderBox);
// adjust sizes of axis, avoid overlaping
if (ordinateSizes)
{
if (params.ordinateAxisPosition === 0) // ordinate on left
abscissaRenderBox.x1 += Math.min(ordinateSizes.total.x, abscissaRenderBox.x2 - abscissaRenderBox.x1); // avoid x1 > x2
else
abscissaRenderBox.x2 -= Math.min(ordinateSizes.total.x, abscissaRenderBox.x2 - abscissaRenderBox.x1); // avoid x1 > x2
}
if (abscissaSizes)
{
if (params.abscissaAxisPosition === 1) // abscissa on top
ordinateRenderBox.y1 += Math.min(abscissaSizes.total.y, ordinateRenderBox.y2 - ordinateRenderBox.y1); // avoid y2 > y1
else
ordinateRenderBox.y2 -= Math.min(abscissaSizes.total.y, ordinateRenderBox.y2 - ordinateRenderBox.y1); // avoid y2 > y1
}
if (drawAbscissa)
{
var scaleLabels = abscissaScaleLabels;
/*
params.abscissaScales?
getScaleRichTexts(params.abscissaScales, params.abscissaScaleFixedDigitCountAfterPoint, params.abscissaScaleBase, params.abscissaScaleUseSciForm):
null;
*/
elem = ARU._drawSingleAxis(drawBridge, richTextDrawer, context, abscissaRenderBox,
actualParams.abscissaDataRange, actualParams.abscissaScales, scaleLabels,
actualParams.abscissaUnitLabel, actualParams.abscissaLabel, abscissaSizes, renderOptions, true, actualParams.abscissaAxisPosition === 1,
actualParams.abscissaReversedDirection);
if (elem)
drawBridge.addToGroup(elem, result);
}
if (drawOrdinate)
{
var scaleLabels = ordinateScaleLabels;
elem = ARU._drawSingleAxis(drawBridge, richTextDrawer, context, ordinateRenderBox,
actualParams.ordinateDataRange, actualParams.ordinateScales, scaleLabels,
actualParams.ordinateUnitLabel, actualParams.ordinateLabel, ordinateSizes, renderOptions, false, actualParams.ordinateAxisPosition !== 1,
actualParams.ordinateReversedDirection);
if (elem)
drawBridge.addToGroup(elem, result);
}
/*
var clientBox = {
'left': abscissaRenderBox.left,
'width': abscissaRenderBox.width,
'top': ordinateRenderBox.top,
'height': ordinateRenderBox.height
};
*/
var clientBox = {
'x1': abscissaRenderBox.x1,
'x2': abscissaRenderBox.x2,
'y1': ordinateRenderBox.y1,
'y2': ordinateRenderBox.y2,
};
return {
'drawnElem': result,
'clientBox': clientBox,
'abscissaRenderBox': abscissaRenderBox,
'ordinateRenderBox': ordinateRenderBox
};
},
/** @private */
_drawSingleAxis: function(drawBridge, richTextDrawer, context, renderBox, dataRange, scales, scaleLabels, unitLabel, axisLabel, elementSizes, renderOptions, isAbscissa, isOnTopOrLeft, isReversedDir)
{
var BXA = Kekule.Render.BoxXAlignment;
var BYA = Kekule.Render.BoxYAlignment;
var alignOnTopOrLeft = isOnTopOrLeft;
var primaryAxis = isAbscissa? 'x': 'y';
var secondaryAxis = isAbscissa? 'y': 'x';
var primaryRectDir = isAbscissa? 'width': 'height';
var secondaryRectDir = isAbscissa? 'height': 'width';
var rOptions = (isAbscissa? renderOptions.abscissa: renderOptions.ordinate) || renderOptions;
//var primarySizeDir = isAbscissa? 'width': 'height';
//var secondarySizeDir = isAbscissa? 'height': 'width';
var stageSize = {'x': renderBox.x2 - renderBox.x1, 'y': renderBox.y2 - renderBox.y1};
if (stageSize.x <= 0 || stageSize.y <= 0) // size is too small to draw
return null;
var result = drawBridge.createGroup(context);
var basePos = {'x': renderBox.x1, 'y': renderBox.y1};
var isMaxValueOnRightOrBottom = isAbscissa? !isReversedDir: isReversedDir;
var coord = {}, coord2 = {};
var drawLabelOptions;
var occupiedSizeOnSecondaryDir = 0;
var elem;
// TODO: currently many drawing options are fixed
// TODO: since the alignRect of richText does not consider sub/sup, need to calculate the pos of all labels with left alignment
// draw axis label
if (axisLabel)
{
coord[primaryAxis] = basePos[primaryAxis] + stageSize[primaryAxis] / 2 - elementSizes.axisLabel[primaryAxis] / 2 * (isAbscissa? 1: -1);
coord[secondaryAxis] = basePos[secondaryAxis]
+ (alignOnTopOrLeft?
(elementSizes.axisLabelPadding[secondaryAxis] + elementSizes.axisLabel[secondaryAxis] / 2):
(stageSize[secondaryAxis] - (elementSizes.axisLabelPadding[secondaryAxis] + elementSizes.axisLabel[secondaryAxis] / 2))
);
drawLabelOptions = Object.extend(Object.extend({}, rOptions.axisLabel),
{'textBoxXAlignment': BXA.LEFT, 'textBoxYAlignment': BYA.CENTER});
if (!isAbscissa)
drawLabelOptions.transforms = [{'rotate': -Math.PI / 2, 'center': Object.extend({}, coord)}];
//elem = drawBridge.drawRichText(context, coord, axisLabel, drawLabelOptions);
var textDrawResult = richTextDrawer.drawEx(context, coord, axisLabel, drawLabelOptions);
elem = textDrawResult.drawnObj;
drawBridge.addToGroup(elem, result);
occupiedSizeOnSecondaryDir += elementSizes.axisLabel[secondaryAxis] + elementSizes.axisLabelPadding[secondaryAxis] * 2;
}
var axisRenderOptions = Object.create(rOptions.axis);
if (!axisRenderOptions.strokeColor)
axisRenderOptions.strokeColor = axisRenderOptions.color;
if (!axisRenderOptions.fillColor)
axisRenderOptions.fillColor = axisRenderOptions.color;
// draw scale markers and scale labels
if (scales && scaleLabels)
{
var dRange = dataRange.max - dataRange.min; // here we should use from/to?
/*
var labelRenderAlignOps = isAbscissa?
{'textBoxXAlignment': BXA.CENTER, 'textBoxYAlignment': alignOnTopOrLeft? BYA.BOTTOM: BYA.TOP}:
{'textBoxXAlignment': alignOnTopOrLeft? BXA.RIGHT: BXA.LEFT, 'textBoxYAlignment': BYA.CENTER};
*/
var labelRenderAlignOps = isAbscissa?
{'textBoxXAlignment': BXA.LEFT, 'textBoxYAlignment': alignOnTopOrLeft? BYA.BOTTOM: BYA.TOP}:
{'textBoxXAlignment': BXA.LEFT, 'textBoxYAlignment': BYA.CENTER};
drawLabelOptions = Object.extend(Object.create(rOptions.scaleLabel), labelRenderAlignOps);
coord[secondaryAxis] = alignOnTopOrLeft?
(basePos[secondaryAxis] + occupiedSizeOnSecondaryDir + elementSizes.scaleLabel[secondaryAxis] + elementSizes.scaleLabelPadding[secondaryAxis]):
(basePos[secondaryAxis] + stageSize[secondaryAxis] - occupiedSizeOnSecondaryDir - elementSizes.scaleLabel[secondaryAxis] - elementSizes.scaleLabelPadding[secondaryAxis]);
var scaleLabelCoord = {};
scaleLabelCoord[secondaryAxis] = coord[secondaryAxis];
//console.log('scaleLabel pos', secondaryAxis, coord[secondaryAxis]);
//drawBridge.drawRect(context, c2, Kekule.CoordUtils.add(c2, elementSizes.scaleLabel), rOptions.axis);
// if the scaleLabel size overlaps each other, we need to bypass some of them
var basedScaleLabelSize = stageSize[primaryAxis] / (scaleLabels.length - 1);
var currScaleLabelSize = basedScaleLabelSize;
var labelPerScales = 1;
while (currScaleLabelSize < elementSizes.scaleLabel[primaryAxis] && basedScaleLabelSize > 0)
{
++labelPerScales;
currScaleLabelSize += basedScaleLabelSize;
}
for (var i = 0, l = scales.length; i < l; ++i)
{
var scalePosDelta = isMaxValueOnRightOrBottom? (scales[i] - dataRange.min): (dataRange.max - scales[i]);
var scalePos = scalePosDelta / dRange * stageSize[primaryAxis] + basePos[primaryAxis];
coord[primaryAxis] = scalePos;
// scale
var scaleLabeled = false;
if (i % labelPerScales === 0)
{
scaleLabelCoord[primaryAxis] = coord[primaryAxis];
if (isAbscissa)
{
var scaleLabelRect = richTextDrawer.measure(context, scaleLabelCoord, scaleLabels[i], drawLabelOptions);
scaleLabelCoord.x = coord.x - scaleLabelRect.width / 2;
} else if (alignOnTopOrLeft) // scale label in ordinate axis, adjust X position when need to align to right
{
var scaleLabelRect = richTextDrawer.measure(context, scaleLabelCoord, scaleLabels[i], drawLabelOptions);
scaleLabelCoord.x = coord.x - scaleLabelRect.width;
}
//elem = drawBridge.drawRichText(context, coord, scaleLabels[i], drawLabelOptions);
var textDrawResult = richTextDrawer.drawEx(context, scaleLabelCoord, scaleLabels[i], drawLabelOptions);
elem = textDrawResult.drawnObj;
/* debug
var bound = textDrawResult.alignRect;
drawBridge.drawRect(context, {x: bound.left, y: bound.top}, {'x': bound.left + bound.width, 'y': bound.top + bound.height}, rOptions);
console.log('scale bound', bound);
*/
drawBridge.addToGroup(elem, result);
scaleLabeled = true;
}
// scale mark
var coord1 = {'x': coord.x, 'y': coord.y};
var scaleMarkSize = elementSizes.scaleMark[secondaryAxis];
coord1[secondaryAxis] += (scaleMarkSize + elementSizes.scaleLabelPadding[secondaryAxis]) * (isOnTopOrLeft? 1: -1);
if (!scaleLabeled)
scaleMarkSize *= (axisRenderOptions.unlabeledScaleSizeRatio || 1);
coord2[primaryAxis] = scalePos;
coord2[secondaryAxis] = coord1[secondaryAxis] - scaleMarkSize * (isOnTopOrLeft? 1: -1);
elem = drawBridge.drawLine(context, coord1, coord2, axisRenderOptions);
drawBridge.addToGroup(elem, result);
}
occupiedSizeOnSecondaryDir += elementSizes.scaleLabel[secondaryAxis] + elementSizes.scaleLabelPadding[secondaryAxis] * 2 + elementSizes.scaleMark[secondaryAxis]; //+ elementSizes.axis[secondaryAxis];
}
// draw axis
coord[primaryAxis] = basePos[primaryAxis];
coord[secondaryAxis] = alignOnTopOrLeft?
(basePos[secondaryAxis] + occupiedSizeOnSecondaryDir + elementSizes.axis[secondaryAxis] / 2):
(basePos[secondaryAxis] + stageSize[secondaryAxis] - occupiedSizeOnSecondaryDir - elementSizes.axis[secondaryAxis] / 2);
coord2[primaryAxis] = coord[primaryAxis] + stageSize[primaryAxis];
coord2[secondaryAxis] = coord[secondaryAxis];
elem = drawBridge.drawLine(context, coord, coord2, Object.extend({'lineCap': 'square'}, axisRenderOptions));
drawBridge.addToGroup(elem, result);
return result;
},
/** @private */
_estimateAxisSizes: function(drawBridge, richTextDrawer, context, renderBox, scaleLabels, unitLabel, axisLabel, renderOptions, isAbscissa, isOnTopOrLeft)
{
var rOptions = (isAbscissa? renderOptions.abscissa: renderOptions.ordinate) || renderOptions;
var scaleLabelSize = {'x': 0, 'y': 0};
var scaleLabelPaddingSize = {'x': 0, 'y': 0};
// here we check the first and last scale labels to roughly determinate the occupied dimensions of scale labels
if (scaleLabels && scaleLabels.length)
{
var scaleLabelsFirst = scaleLabels[0];
var scaleLabelsLast = scaleLabels[scaleLabels.length - 1];
var scaleLabelFirstDim = richTextDrawer.measure(context, {'x': 0, 'y': 0}, scaleLabelsFirst, rOptions.scaleLabel);
var scaleLabelLastDim = richTextDrawer.measure(context, {'x': 0, 'y': 0}, scaleLabelsLast, rOptions.scaleLabel);
scaleLabelSize = {'x': Math.max(scaleLabelFirstDim.width, scaleLabelLastDim.width), 'y': Math.max(scaleLabelFirstDim.height, scaleLabelLastDim.height)};
scaleLabelPaddingSize = {'x': (rOptions.scaleLabel.padding || 0), 'y': (rOptions.scaleLabel.padding || 0)};
}
// the unit label
var unitLabelSize = {'x': 0, 'y': 0};
if (unitLabel)
{
var unitLabelDim = richTextDrawer.measure(context, {'x': 0, 'y': 0}, unitLabel, rOptions.unitLabel);
unitLabelSize.x = Math.max(scaleLabelSize.x, unitLabelDim.width);
unitLabelSize.y = Math.max(scaleLabelSize.y, unitLabelDim.height);
}
// the axis label
var axisLabelSize = {'x': 0, 'y': 0};
var axisLabelPaddingSize = {'x': 0, 'y': 0};
if (axisLabel)
{
var axisLabelDim = richTextDrawer.measure(context, {'x': 0, 'y': 0}, axisLabel, rOptions.axisLabel);
if (isAbscissa)
axisLabelSize = {'x': axisLabelDim.width, 'y': axisLabelDim.height};
else // for ordinate axis, we need to draw the label with 90 deg rotation
axisLabelSize = {'y': axisLabelDim.width, 'x': axisLabelDim.height};
// console.log('axisLabelSize', Kekule.Render.RichTextUtils.toText(axisLabel), axisLabelSize);
axisLabelPaddingSize = {'x': (rOptions.axisLabel && rOptions.axisLabel.padding || 0), 'y': (rOptions.axisLabel && rOptions.axisLabel.padding || 0)};
}
// the scale marks
var axisOptions = rOptions.axis;
var scaleMarkSize = isAbscissa? {'x': 0, 'y': axisOptions.scaleMarkSize}: {'x': axisOptions.scaleMarkSize, 'y': 0};
// the axis
var axisSize = isAbscissa? {'x': 0, 'y': axisOptions.strokeWidth}: {'x': axisOptions.strokeWidth, 'y': 0};
// sum up
var totalSize = {};
if (isAbscissa)
{
totalSize.x = renderBox.x2 - renderBox.x1;
totalSize.y = scaleLabelSize.y + axisLabelSize.y + axisSize.y + scaleMarkSize.y + scaleLabelPaddingSize.y * 2 + axisLabelPaddingSize.y * 2;
}
else
{
totalSize.y = renderBox.y2 - renderBox.y1;
totalSize.x = scaleLabelSize.x + axisLabelSize.x + axisSize.x + scaleMarkSize.x + scaleLabelPaddingSize.x * 2 + axisLabelPaddingSize.x * 2;
}
var result = {
total: totalSize,
axis: axisSize,
scaleMark: scaleMarkSize,
scaleLabel: scaleLabelSize,
scaleLabelPadding: scaleLabelPaddingSize,
unitLabel: unitLabelSize,
axisLabel: axisLabelSize,
axisLabelPadding: axisLabelPaddingSize,
};
return result;
}
};
var ARU = Kekule.Render.CoordAxisRender2DUtils;
/**
* Some util functions for 2D spectrum rendering.
* @class
*/
Kekule.Render.Spectrum2DRenderUtils = {
/**
* Returns false if the axis direction should be reversed.
* @param {String} spectrumType
* @param {Bool} isIndependent
* @param {String} axisUnitSymbol
* @private
*/
_getDataDefaultAxisDirection: function(spectrumType, isIndependent, axisUnitSymbol)
{
var ST = Kekule.Spectroscopy.SpectrumType;
var KU = Kekule.Unit;
var result = true;
if (spectrumType === ST.NMR)
{
if (isIndependent) // ppm, Hz... should be from right to left
result = false;
}
if (spectrumType === ST.IR)
{
if (isIndependent)
{
if (axisUnitSymbol)
{
var unitObj = KU.getUnit(axisUnitSymbol);
if (unitObj.category === KU.WaveNumber)
return false;
}
/*
if (axisUnitSymbol === KU.WaveNumber.RECIPROCAL_CENTIMETER)
result = false;
*/
}
}
return result;
},
/**
* Returns false if the axis should be at the left/bottom rather than the right/top.
* @param {String} spectrumType
* @param {Bool} isIndependent
* @param {String} axisUnitSymbol
* @private
*/
_getDataDefaultAxisAlign: function(spectrumType, isIndependent, axisUnitSymbol)
{
var ST = Kekule.Spectroscopy.SpectrumType;
var KU = Kekule.Unit;
var result = true;
return result;
},
getDefaultAxisDirectionAndAlignInfo: function(spectrumType, isIndependant, axisUnitSymbol)
{
var defaultDirection = Kekule.Render.Spectrum2DRenderUtils._getDataDefaultAxisDirection(spectrumType, isIndependant, axisUnitSymbol);
var defaultAlign = Kekule.Render.Spectrum2DRenderUtils._getDataDefaultAxisAlign(spectrumType, isIndependant, axisUnitSymbol);
return {
'reversedDirection': !defaultDirection,
'reversedAlign': !defaultAlign
}
}
};
/**
* Base spectrum render.
* @class
* @augments Kekule.Render.ChemObj2DRenderer
*/
Kekule.Render.Spectrum2DRenderer = Class.create(Kekule.Render.ChemObj2DRenderer,
/** @lends Kekule.Render.Spectrum2DRenderer# */
{
/** @private */
CLASS_NAME: 'Kekule.Render.Spectrum2DRenderer',
/** @constructs */
initialize: function(chemObj, drawBridge, parent)
{
this.tryApplySuper('initialize', [chemObj, drawBridge, parent]);
this._initSectionDataDrawerMap();
},
/** @private */
_initSectionDataDrawerMap: function()
{
Kekule.Render.Spectrum2DRenderer.sectionDataDrawerMap[Kekule.Spectroscopy.DataMode.CONTINUOUS] = this.doDrawContinuousSectionData;
Kekule.Render.Spectrum2DRenderer.sectionDataDrawerMap[Kekule.Spectroscopy.DataMode.PEAK] = this.doDrawPeakSectionData;
},
/** @private */
_initSpectrumDefSize: function(chemObj, drawOptions)
{
if (!chemObj.getSize2D()) // the spectrum size has not been set yet, use the default one in render config
{
var size2D = CU.multiply(drawOptions.spectrum_defSize2DRatio, drawOptions.defScaleRefLength * (drawOptions.unitLength || 1));
chemObj.setSize2D(size2D);
}
},
/* @ignore */
/*
getChildObjs: function()
{
var spectrumData = this.getChemObj() && this.getChemObj().getData();
var section = spectrumData && spectrumData.getActiveSection();
var result = this.tryApplySuper('getChildObjs');
return result? result.concat(section): [section];
},
*/
/** @ignore */
doDrawSelf: function(context, baseCoord, options)
{
var addSubBounds = options._spectrum_render_enable_sub_bounds; // a special flag, whether to save the bound of sub elems (axis, client...)
//console.log('render options in renderer', options);
this.tryApplySuper('doDrawSelf', [context, baseCoord, options]);
var chemObj = this.getChemObj();
if (!baseCoord)
baseCoord = this.getAutoBaseCoord(options);
this._initSpectrumDefSize(chemObj, options);
// debug
//options.spectrum_reversedAxises = true;
// calc context size of image
var objBox = chemObj.getExposedContainerBox();
var coord1 = {x: objBox.x1, y: objBox.y2};
var coord2 = {x: objBox.x2, y: objBox.y1};
var contextCoord1 = this.transformCoordToContext(context, chemObj, coord1);
var contextCoord2 = this.transformCoordToContext(context, chemObj, coord2);
var size = Kekule.CoordUtils.substract(contextCoord2, contextCoord1);
// since baseCoord is at the center of object, we need calculate out the corner coord
var drawCoord = {x: baseCoord.x - size.x / 2, y: baseCoord.y - size.y / 2};
var contextBoxCoord1 = drawCoord;
var contextBoxCoord2 = Kekule.CoordUtils.add(drawCoord, size);
var contextBox = Kekule.BoxUtils.createBox(contextBoxCoord1, contextBoxCoord2);
//console.log('draw', baseCoord, drawCoord, options, objBox, size);
var actualRenderOptions = this._getActualRenderOptions(context, chemObj, options);
var result;
var sections = this.doGetTargetDataSections(chemObj);
if (sections && sections.length) // Need to do concrete drawing
{
var varInfos = this.doGetDataVarInfos(sections);
if (varInfos) // we can not render without proper variable information
{
// check the axis alignment and direction
var axisDirectionAndAlignInfo = this._getAxisDirectionAndAlignInfo(context, chemObj, varSymbols, varInfos.varUnitSymbols, actualRenderOptions);
var visibleDataRange = this._getDisplayRangeOfSections(chemObj, chemObj.getData(), sections, varInfos, axisDirectionAndAlignInfo, actualRenderOptions);
var varSymbols = varInfos.varSymbols;
if (Kekule.NumUtils.isFloatEqual(visibleDataRange[varSymbols.independant].min, visibleDataRange[varSymbols.independant].max)
|| Kekule.NumUtils.isFloatEqual(visibleDataRange[varSymbols.dependant].min, visibleDataRange[varSymbols.dependant].max)) // visible range is empty
{
Kekule.error(Kekule.$L('ErrorMsg.VISIBLE_DATA_RANGE_IS_EMPTY'));
return null; // do not need to do concrete drawing
}
result = this.createDrawGroup(context);
var clientContextBox;
// indicator elements
// note: since here we call getDrawBridge() directly, the context should be returned by getActualTargetContext()
var indicatorDrawParamsAndOptions = this._prepareAxisRenderParamsAndOptions(this.getActualTargetContext(context), chemObj, visibleDataRange, varSymbols, actualRenderOptions, axisDirectionAndAlignInfo);
var indicatorDrawResult = Kekule.Render.CoordAxisRender2DUtils.drawAxises(this.getDrawBridge(), this.getRichTextDrawer(), this.getActualTargetContext(context), contextBox,
indicatorDrawParamsAndOptions.drawParams, indicatorDrawParamsAndOptions.renderOptions);
// save the reversed axises information in cache
this.getRenderCache(context).spectrum_reversedAxises = actualRenderOptions.spectrum_reversedAxises;
//console.log(indicatorDrawResult);
if (indicatorDrawResult)
{
this.addToDrawGroup(indicatorDrawResult.drawnElem, result);
clientContextBox = indicatorDrawResult.clientBox;
if (addSubBounds)
{
if (indicatorDrawResult.abscissaRenderBox)
this.basicDrawObjectUpdated(context, chemObj.getPseudoRenderSubObject('abscissaIndicator'), chemObj,
this.createRectBoundInfo(
{'x': indicatorDrawResult.abscissaRenderBox.x1, 'y': indicatorDrawResult.abscissaRenderBox.y1},
{'x': indicatorDrawResult.abscissaRenderBox.x2, 'y': indicatorDrawResult.abscissaRenderBox.y2}
), Kekule.Render.ObjectUpdateType.ADD);
if (indicatorDrawResult.ordinateRenderBox)
this.basicDrawObjectUpdated(context, chemObj.getPseudoRenderSubObject('ordinateIndicator'), chemObj,
this.createRectBoundInfo(
{'x': indicatorDrawResult.ordinateRenderBox.x1, 'y': indicatorDrawResult.ordinateRenderBox.y1},
{'x': indicatorDrawResult.ordinateRenderBox.x2, 'y': indicatorDrawResult.ordinateRenderBox.y2}
), Kekule.Render.Object