UNPKG

kekule

Version:

Open source JavaScript toolkit for chemoinformatics

1,126 lines (1,060 loc) 108 kB
/** * @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