Source: render/kekule.render.base.js

/**
 * @fileoverview
 * Base (abstract) class of 2D or 3D molecule renderers.
 * @author Partridge Jiang
 */

/*
 * requires /lan/classes.js
 * requires /core/kekule.common.js
 * requires /render/kekule.render.utils.js
 */

"use strict";

/**
 * Namespace for renderer system.
 * @namespace
 */
Kekule.Render = {};

/**
 * Enumeration of coord systems (chemObject, context or screen).
 * @class
 */
Kekule.Render.CoordSystem = {
	/** ChemObject system, used to mark the inner structure of chem objects. 2D or 3D. */
	CHEM: 0,
	/** Context system, used for draw bridges. 2D or 3D. */
	CONTEXT: 1,
	/** Screen system, 2D, usually in pixel. */
	SCREEN: 2
};

/*
 * Enumeration of interaction states of chem object (node or connector).
 * @class
 * @deprecated
 */
//Kekule.Render.InteractState = {
	/** Normal state. */
	//NORMAL: 'normal',
	/** Mouse hover above. */
	//HOVER: 'hover',
	/** Object is selected. */
	//ACTIVE: 'active'
//};

/**
 * Enumeration of renderer types: 2D or 3D.
 * @enum
 */
Kekule.Render.RendererType = {
	/** 2D renderer. */
	R2D: 2,
	/** 3D renderer. */
	R3D: 3
};

/**
 * The position of coord point in object box.
 * @enum
 */
Kekule.Render.CoordPos = {
	/** Center point of object. */
	CENTER: 0,
	/** Top left point of object, usually used in blocks of 2D context. */
	CORNER_TL: 21,
	/** Default value, same as CENTER. */
	DEFAULT: 0
};

/**
 * C is the most common element in organic molecule. So the label of C will be ignored in bond-line formula.
 * @constant
 */
Kekule.Render.DEF_ATOM_ATOMIC_NUM = 6;

/**
 * Enumeration of molecule display type, condensed formula or bond-line formula.
 * @enum
 */
Kekule.Render.MoleculeDisplayType = {
	/** bond-line formula */
	SKELETAL: 1,
	/** Condensed formula */
	CONDENSED: 2,
	DEFAULT: 1
};
Kekule.Render.Molecule2DDisplayType = Kekule.Render.MoleculeDisplayType;

/**
 * Enumeration of node label (usually atom label) display mode (especially in 2D renderer).
 * @enum
 */
Kekule.Render.NodeLabelDisplayMode = {
	/** Label is hidden */
	HIDDEN: -1,
	/** Label should be shown */
	SHOWN: 1,
	/** Whether show label is decided by the display type of molecule */
	SMART: 0,
	/** Default is SMART */
	DEFAULT: 0
}

/**
 * Enumeration of hydrongen display strategy (espcially in 2D renderer).
 * @enum
 */
Kekule.Render.HydrogenDisplayLevel = {
	/** No hydrongen is displayed */
	NONE: 0,
	/** Only display explicit hydrogens. */
	EXPLICIT: 1,
	/** Display explicit hydrogens only when the count is not the same as implicit. */
	UNMATCHED_EXPLICIT: 2,
	/** Display all hydrogens, whether explicit or implicit ones. */
	ALL: 10,
	/** Default is EXPLICIT. */
	DEFAULT: 1
};


/**
 * Enumeration of direction of text (especially rich text label).
 * @enum
 */
Kekule.Render.TextDirection = {
	/** @ignore */
	DEFAULT: 0,
	/** Left to right. */
	LTR: 1,
	/** Right to left. */
	RTL: 3,
	/** Top to bottom. */
	TTB: 2,
	/** Bottom to top */
	BTT: 4,
	/** Inherit from parent setting */
	INHERIT: 10
	/* Decide the direction by environment around. Useful when draw label in ctab. */
	/*SMART: 20*/
};


/**
 * Enumeration of horizontal / vertical alignment of text.
 * @enum
 */
Kekule.Render.TextAlign = {
	/*BASELINE: 0,  //??????*/
	DEFAULT: 0,
	LEFT: 1,
	RIGHT: 2,
	TOP: 3,
	BOTTOM: 4,
	CENTER: 5,
	LEADING: 10,
	TRAILING: 11,

	/**
	 * LEADING and TRAILING are related align mode, related to text direction.
	 * This function returns absolute align value due to textDirection.
	 * @returns {Int}
	 */
	getAbsAlign: function(textAlign, textDirection)
	{
		var result = textAlign;
		var TA = Kekule.Render.TextAlign;
		var TD = Kekule.Render.TextDirection;
		if ((textAlign === TA.LEADING) || (textAlign === TA.TRAILING))
		{
			var isLeading = textAlign == TA.LEADING;
			switch (textDirection)
			{
				case TD.RTL: result = isLeading? TA.RIGHT: TA.LEFT; break;
				case TD.TTB: result = isLeading? TA.TOP: TA.BOTTOM; break;
				case TD.BTT: result = isLeading? TA.BOTTOM: TA.TOP; break;
				case TD.LTR:
				default:
					result = isLeading? TA.LEFT: TA.RIGHT; break;
			}
		}
		return result;
	}
};

/**
 * Enumeration of alignment types of box in horizontal direction.
 * @enum
 */
Kekule.Render.BoxXAlignment = {
	LEFT: 0,
	RIGHT: 1,
	CENTER: 2
};
/**
 * Enumeration of alignment types of box in vertical direction.
 * @enum
 */
Kekule.Render.BoxYAlignment = {
	TOP: 0,
	BOTTOM: 1,
	CENTER: 2
};

/**
 * Enumeration of alignment mode of a rich text box.
 * @enum
 */
Kekule.Render.TextBoxAlignmentMode = {
	/** Alignment based on the whole text box */
	BOX: 0,
	/** Alignment based on the childmost anchor item of rich text */
	ANCHOR: 1
};

/**
 * Enumeration of types of rendering a bond line.
 * @enum
 */
Kekule.Render.BondRenderType = {
	/** Usual single bond, draw in a thin line */
	SINGLE: 0,
	/** Usual double bond, draw in thin double line */
	DOUBLE: 1,
	/** Usual triple bond, draw in thin triple line */
	TRIPLE: 2,
	/* Usual quad bond, draw in thin quad line */
	QUAD: 3,
	/** Dashed bond line */
	DASHED: 4,
	/** Dashed double line */
	DASHED_DOUBLE: 5,
	/** Dashed triple line */
	DASHED_TRIPLE: 6,
	/** A sold and a dashed line, usually used for aromatic bond */
	SOLID_DASH: 7,
	/** A line with a arrow in the end, usually for coordinate-bond */
	ARROWED: 8,
	/** A line with a arrow in the head, usually for coordinate-bond */
	ARROWED_INV: 9,
	/** A hashed line */
	HASHED: 10,
	/** A bold line, usually for bond above paper */
	BOLD: 11,
	/** A bold and a normal line, usually for double bond above paper */
	BOLD_DOUBLE: 12,
	/** A bold and two normal line, usually for triple bond above paper */
	BOLD_TRIPLE: 13,
	/* A bold and three normal line, usually for quad bond above paper */
	BOLD_QUAD: 14,
	/** A bold and a dash line, usually for aromatic bond above paper */
	BOLD_DASH: 16,
	/** A solid wedge triangle from atom 1 to atom 2, usually for wedge up bond */
	WEDGED_SOLID: 20,
	/** A solid wedge triangle from atom 2 to atom 1, usually for wedge up bond */
	WEDGED_SOLID_INV: 21,
	/** A hollow wedge triangle from atom 1 to atom 2,  usually for wedge up bond */
	WEDGED_HOLLOW: 22,
	/** A hollow wedge triangle from atom 2 to atom 1, usually for wedge up bond */
	WEDGED_HOLLOW_INV: 23,
	/** A hased wedge triangle from atom 1 to atom 2, usually for wedge down bond */
	WEDGED_HASHED: 24,
	/** A hased wedge triangle from atom 2 to atom 1, usually for wedge down bond */
	WEDGED_HASHED_INV: 25,
	/** A bold rectangle, indicating a bond near the observer. Usually connected with wedged bonds. */
	WEDGED_SOLID_BOTH: 26,
	/** A bold hollow rectangle, indicating a bond near the observer. Usually connected with wedged bonds. */
	WEDGED_HOLLOW_BOTH: 27,
	/** A wavy line, usually used for bond with uncertain stereo */
	WAVY: 30,
	/** A cross double bond, means an uncertain E or Z stereo */
	SCISSORS_DOUBLE: 40
};


/**
 * Enumeration of types to render a charge on atom.
 * @enum
 */
Kekule.Render.ChargeMarkRenderType = {
	/** Number + symbol, such as 2+, 3- */
	NUM_WITH_SYMBOL: 1,
	DEFAULT: 1,
	/** Only symbol, such as ++, = */
	//SYMBOL_ONLY: 2,
	/** Surrond with a circle to emphasis, the circle will only be draw when charge = +1/-1 */
	CIRCLE_AROUND: 3
};

/**
 * Enumeration of graphic quality levels to render objects in 3D.
 * @enum
 */
Kekule.Render.Render3DGraphicQuality = {
	EXTREME_LOW: 1,
	LOW: 2,
	MEDIUM: 3,
	HIGH: 4,
	EXTREME_HIGH: 5
};

/**
 * Enumeration of types to render a molecule in 3D.
 * @enum
 */
Kekule.Render.Molecule3DDisplayType = {
	/** Wire frame */
	WIRE: 31,
	/** Sticks */
	STICKS: 32,
	/** Ball and stick */
	BALL_STICK: 33,
	/** Space fill */
	SPACE_FILL: 34,
	/** Default is ball and stick */
	DEFAULT: 33
};

/**
 * Enumeration of types to render a bond in 3D.
 * @enum
 */
Kekule.Render.Bond3DRenderMode = {
	/** do not render bond. */
	NONE: 0,
	/** One wire is used to represent one bond (multiple or not). */
	WIRE: 1,
	/** Multiple wires are used for multiple bond. */
	MULTI_WIRE: 2,
	/** One cylinder is used to represent one bond (multiple or not). */
	CYLINDER: 3,
	/** Use multiple cylinders for multiple bond. */
	MULTI_CYLINDER: 4,
	/**
	 * Check if connector / bond should be draw in lines.
	 * @param {Int} mode
	 * @returns {Bool}
	 */
	isWireMode: function(mode)
	{
		var M = Kekule.Render.Bond3DRenderMode;
		return (mode === M.WIRE) || (mode === M.MULTI_WIRE);
	},
	/**
	 * Check if connector / bond should be draw in cylinders.
	 * @param {Int} mode
	 * @returns {Bool}
	 */
	isCylinderMode: function(mode)
	{
		var M = Kekule.Render.Bond3DRenderMode;
		return (mode === M.CYLINDER) || (mode === M.MULTI_CYLINDER);
	}
};

/**
 * Enumeration of types to decide how a bond is splitted in 3D render.
 * @enum
 */
Kekule.Render.Bond3DSpliceMode = {
	/** Bond draw as a whole, not split. */
	UNSPLIT: 1,
	/** Split from the middle, as two line with the same length. */
	MID_SPLIT: 2,
	/** Split, a biger atom gains biger part of bond */
	WEIGHTING_SPLIT: 3
};

/**
 * Enumeration of types to draw a connector (bond) in 3D render.
 * @enum
 */
Kekule.Render.Bond3DRenderType = {
	/** Just one line or cylinder, used for most bonds. */
	SINGLE: 1,
	/** Double lines or cylinders, used for double bond. */
	DOUBLE: 2,
	/** Triple lines or cylinders, used for triple bond. */
	TRIPLE: 3,
	/** Dash line or cylinder, usually used for H-bond. */
	DASH: -1,
	/** One solid and a dash line or cylinder, used for aromatic bond. */
	SOLID_DASH: -2
};


/**
 * Enumeration of types to draw a node (atom) in 3D render.
 * @enum
 */
Kekule.Render.Node3DRenderMode = {
	/** Do not render explicit atom, used in WIRE display mode. */
	NONE: 0,
	/** Render atom as ball, used in BALL_STICK display mode. */
	BALL: 1,
	/** Render atom as a huge ball, according to Vdw radius, used in SPACE_FILL display mode. */
	SPACE: 2
	///** Render as small ball at the end of bond, used in STICKS display mode */
	//SMALL_CAP: 3
};

/**
 * Enumeration of shape types to describle meta shape info.
 * @enum
 */
Kekule.Render.MetaShapeType = {
	// 2D shapes
	/** A single point on context. Can be determinated by a single coord ({[coord]}). */
	POINT: 0,
	/** A circle on context. Can be determinated by a single coord and a radius. ({[coord], radius}) */
	CIRCLE: 1,
	/** A line on context, determinated by two coords and a width property ({[coord1, coord2], width). */
	LINE: 2,
	/** A rectangle on context, determinated by two coords ({[coord1, coord2]}). */
	RECT: 3,
	/** An arc  on context, determinated by a single coord and radius, startAngle, endAngle, anticlockwise. */
	ARC: 5,
	/** Unclosed polyline, determinated by a set of coords ({[coord1, coord2, coord3, ... }). */
	POLYLINE: 11,
	/** Polygon, determinated by a set of coords ({[coord1, coord2, coord3, ... }). */
	POLYGON: 10,
	// 3D shapes
	/** A shpere on 3D context. Can be determinated by a single coord and a radius. ({[coord], radius}) */
	SPHERE: 101,
	/** Cylinder in 3D context. Can be determinated by two coords and a radius. ({[coord1, coord2], radius}) */
	CYLINDER: 102,
	/**
	 * A complex shape composited of a series of child shapes.
	 * In implementation, an array of meta shapes will map to this type.
	 */
	COMPOSITE: -1
};
// Alias of MetaShapeType
Kekule.Render.BoundShapeType = Kekule.Render.MetaShapeType;

/**
 * Enumeration of types of updating a object.
 * @enum
 */
Kekule.Render.ObjectUpdateType = {
	/** Modify a existing object. */
	MODIFY: 0,
	/** Add a new object. */
	ADD: 1,
	/** Remove a existing object. */
	REMOVE: 2,
	/** Clear whole object. */
	CLEAR: 3
};

/**
 * Base class for different types of concrete renderers.
 * @class
 * @augments ObjectEx
 * @param {Kekule.ChemObject} chemObj Object to be drawn.
 * @param {Object} drawBridge Concrete draw bridge to do the actual draw job.
 * //@param {Object} renderConfigs Configuration for rendering.
 * //  This property should be an instance of {@link Kekule.Render.Render2DConfigs} or {@link Kekule.Render.Render3DConfigs}.
 * @param {Kekule.ObjectEx} parent Parent object of this renderer, usually another renderer or an instance of {@link Kekule.Render.ChemObjPainter}, or null.
 *
 * @property {Kekule.ChemObject} chemObj Object to be drawn, read only.
 * @property {Object} drawBridge Concrete draw bridge to do the actual draw job.
 * //@property {Object} renderConfigs Configuration for rendering.
 * //  This property should be an instance of {@link Kekule.Render.Render2DConfigs} or {@link Kekule.Render.Render3DConfigs}
 * //@property {Object} renderCache Stores params (center coord, options, matrix...) on last draw process.
 * @property {Kekule.ObjectEx} parent Parent object of this renderer, usually another renderer or an instance of {@link Kekule.Render.ChemObjPainter}.
 * @property {Kekule.Render.AbstractRenderer} parentRenderer Parent renderer that calls this one.
 *
 * @property {Bool} canModifyTargetObj If set to true, renderer may change the rendered object (e.g., add charge markers, change block sizes...).
 *   This property can inherit value from parent.
 * @property {Object} redirectContext If this property is set, renderer will draw on this one instead if the context in drawXXX methods.
 *   This property is used by editor. User should utilize this property with caution.
 */
/**
 * Invoked when a basic object (node, connector, glyph...) is drawn, updated or removed.
 *   event param of it has fields: {obj, parentObj, boundInfo, updateType}
 *   where boundInfo provides the bound box information of this object on context. It has the following fields:
 *   {
 *     context: drawing context object
 *     obj: drawn object
 *     parentObj: parent of drawn object
 *     boundInfo: a hash containing info of bound, including fields:
 *     {
	 *     shapeType: value from {@link Kekule.Render.MetaShapeType} or {@link Kekule.Render.Meta3DShapeType}.
	 *     coords: [Array of coords]
	 *   }
 *     updateType: add, modify or remove
 *   }
 *   boundInfo may also be a array for complex situation (such as multicenter bond): [boundInfo1, boundInfo2, boundInfo3...].
 *   Note that in removed event, boundInfo may be null.
 * @name Kekule.Render.AbstractRenderer#updateBasicDrawObject
 * @event
 */
/**
 * Invoked when whole chem object (molecule, reaction...) is prepared to be drawn in context.
 *   event param of it has two fields: {context, obj}
 * @name Kekule.Render.AbstractRenderer#prepareDrawing
 * @event
 */
/**
 * Invoked when whole chem object (molecule, reaction...) is drawn in context.
 *   event param of it has two fields: {context, obj}
 * @name Kekule.Render.AbstractRenderer#draw
 * @event
 */
/**
 * Invoked when whole chem object (molecule, reaction...) is cleared from context.
 *   event param of it has two fields: {context, obj}
 * NOTE: this event is not well implemented and may be buggy.
 * @name Kekule.Render.AbstractRenderer#clear
 * @event
 */
Kekule.Render.AbstractRenderer = Class.create(ObjectEx,
/** @lends Kekule.Render.AbstractRenderer# */
{
	/** @private */
	CLASS_NAME: 'Kekule.Render.AbstractRenderer',
	/** @private */
	RENDER_CACHE_FIELD: '__$renderCache$__',
	/** @constructs */
	initialize: function($super, chemObj, drawBridge, /*renderConfigs,*/ parent)
	{
		$super();
		this.setPropValue('chemObj', chemObj, true); // since we have no setChemObj method, use this instead
		/*
		if (renderConfigs)
			this.setRenderConfigs(renderConfigs);
		*/
		if (parent)
			this.setParent(parent);
		this.setDrawBridge(drawBridge);

		this.setBubbleEvent(true);  // allow event bubble

		this._suspendUpdateStatus = 0;  // used internal
		this._suspendUpdateInfos = [];
	},
	finalize: function($super)
	{
		//console.log('release renderer', this.getClassName());
		this.setPropValue('chemObj', null, true);
		this.setDrawBridge(null);
		$super();
	},
	/** @private */
	initProperties: function()
	{
		this.defineProp('chemObj', {'dataType': 'Kekule.ChemObject', 'serializable': false, 'setter': null});  // readonly
		/*
		this.defineProp('baseCoord', {'dataType': DataType.HASH, 'serializable': false,
			'getter': function()
			{
				var result = this.getPropStoreFieldValue('baseCoord');
				if (!result)
					result = this.getAutoBaseCoord();
				return result;
			}
		});
		*/
		this.defineProp('drawBridge', {'dataType': DataType.OBJECT, 'serializable': false});
		//this.defineProp('renderConfigs', {'dataType': DataType.OBJECT, 'serializable': false});
		//this.defineProp('renderCache', {'dataType': DataType.OBJECT, 'serializable': false});
		this.defineProp('parent', {'dateType': DataType.OBJECT, 'serializable': false});
		this.defineProp('parentRenderer',
			{
				'dateType': 'Kekule.Render.AbstractRenderer', 'serializable': false,
				'getter': function()
					{
						var p = this.getParent();
						return (p instanceof Kekule.Render.AbstractRenderer)? p: null;
					},
				'setter': function(value)
					{
						this.setParent(value);
					}
			});

		this.defineProp('redirectContext', {'dataType': DataType.OBJECT, 'serializable': false});
		this.defineProp('canModifyTargetObj', {'dataType': DataType.BOOL, 'serializable': false,
			'getter': function()
			{
				var result = this.getPropStoreFieldValue('canModifyTargetObj');
				if (Kekule.ObjUtils.isUnset(result))
				{
					var p = this.getParent();
					if (p && p.getCanModifyTargetObj)
						result = p.getCanModifyTargetObj();
				}
				return result;
			}
		});

		this.defineEvent('clear');
		this.defineEvent('updateBasicDrawObject');
	},

	/**
	 * Check if current renderer is the topmost one (without parent renderer, but maybe has parent painter).
	 */
	isRootRenderer: function()
	{
		return !this.getParentRenderer();
	},

	/** @private */
	getHigherLevelObj: function()
	{
		return this.getParent();
	},

	/**
	 * Report the type (2D or 3D) of this renderer.
	 * @returns {Int} Value from {@link Kekule.Render.RendererType}.
	 */
	getRendererType: function()
	{
		return Kekule.Render.RendererType.R2D;  // default is 2D renderer
	},
	/**
	 * Report coord mode of this renderer.
	 * @returns {Int} Value from {@link Kekule.CoordMode}.
	 */
	getCoordMode: function()
	{
		var rType = this.getRendererType();
		return (rType === Kekule.Render.RendererType.R3D)? Kekule.CoordMode.COORD3D: Kekule.CoordMode.COORD2D;
	},

	/**
	 * Returns draw params (center coord, options, matrix...) on last draw process on context.
	 * @returns {Hash}
	 */
	getRenderCache: function(context)
	{
		var result = this.getExtraProp(context, this.RENDER_CACHE_FIELD);
		if (!result)
		{
			result = {};
			this.setExtraProp(context, this.RENDER_CACHE_FIELD, result);
		}
		return result;
		/*
		var result = this._cache; //[this.RENDER_CACHE_FIELD];
		if (!result)
		{
			this._cache = {'field': 'value'};
			result = this._cache;
			console.log('initial render cache', this.getClassName());
		}
		return result;
		*/
	},

	/**
	 * A method that will be called before draw().
	 * Some preparation job can be done here.
	 * Note that only the root renderer will call this method.
	 * @private
	 */
	beginDraw: function(context, baseCoord, options)
	{
		var b = this.getDrawBridge();
		if (b.prepareContext)
			b.prepareContext(context);
	},
	/**
	 * A method that will be called after draw().
	 * Note that only the root renderer will call this method.
	 * @private
	 */
	endDraw: function(context, baseCoord, options)
	{
		// some draw bridge (such as Three.js need to call render method to do actual draw job.
		var b = this.getDrawBridge();
		if (b.renderContext)
			b.renderContext(context);
	},

	/** @private */
	updateDrawInfoInCache: function(chemObj, context, baseCoord, options, realDrawOptions)
	{
		var p = this.getRenderCache(context);
		if (context)
			p.context = context;
		if (baseCoord)
			p.baseCoord = baseCoord;
		if (chemObj)
			p.chemObj = chemObj;
		if (options)
			p.options = options;
		if (realDrawOptions)
			p.realDrawOptions = realDrawOptions;
	},

	/** @private */
	_isCurrChemObjNeedToBeDrawn: function(partialDrawObjs, context)
	{
		var selfObj = this.getChemObj();
		for (var i = 0, l = partialDrawObjs.length; i < l; ++i)
		{
			var obj = partialDrawObjs[i];
			if ((obj === selfObj) || (obj.isChildOf(selfObj)))
				return true;
		}
		return false;
	},

	/**
	 * Auto calculate draw context coord by coord of chem obj. When no baseCoord is provided in draw method,
	 * this result may be used instead.
	 * @param {Hash} drawOptions
	 * @returns {Hash}
	 */
	getAutoBaseCoord: function(drawOptions)
	{
		return this.doGetAutoBaseCoord(drawOptions);
	},
	/**
	 * Do actual work of getAutoBaseCoord.
	 * Descendants need to override this method.
	 * @param {Hash} drawOptions
	 * @returns {Hash}
	 * @private
	 */
	doGetAutoBaseCoord: function(drawOptions)
	{
		return null;
	},

	/** @ignore */
	getCachedDrawOptions: function(context)
	{
		return this.getRenderCache(context).options;
	},
	/** @ignore */
	getCachedDrawnElem: function(context)
	{
		return this.getRenderCache(context).drawnElem;
	},

	/**
	 * Draw an instance of ChemObject to context.
	 * The actual job is done in doDraw method. Descendants should override doDraw.
	 * @param {Object} context Context to be drawn, such as Canvas, SVG, VML and so on.
	 * @param {Hash} baseCoord Base coord to draw this object, can be null to use coord of chemObj itself.
	 *   This coord is based on context.
	 * @param {Hash} options Draw options, such as draw rectangle, draw style, zoom and so on.
	 *   Different renderer may requires different option params.
	 *   In options hash object, there may be one special array field: partialDrawObjs. If this field is set,
	 *   then only chem objects in this array will be actually drawn.
	 * @returns {Object} Drawn element on context (such as SVG) or null on direct context (such as canvas).
	 */
	draw: function(context, baseCoord, options)
	{
		//console.log('[Draw]', this.getClassName(), this.getChemObj().getId? this.getChemObj().getId(): null);
		/*
		var p = this.getRenderCache(context);
		p.context = context;
		p.baseCoord = baseCoord;
		p.chemObj = this.getChemObj();
		*/

		//console.log('baseDraw', baseCoord, p);
		//this.updateDrawInfoInCache(this.getChemObj(), context, baseCoord, options);
		try
		{
			this.__isDrawing = true;  // flag avoid duplicated draw
			var ops = {};
			// actual draw options should also inherited from parent renderer
			var parentOps;
			var parent = this.getParentRenderer();
			if (parent)
			{
				var parentOps = parent.getRenderCache().options;
				//console.log('parent', this.getClassName(), parentOps);
				/*
				 if (parentOps)
				 ops = Object.create(parentOps);
				 */
			}
			if (parentOps)
			{
				ops = Object.create(parentOps);
				ops = Object.extend(ops, options);  // self options should override parent one
			}
			else
				ops = Object.create(options || null);

			var chemObj = this.getChemObj();

			var partialDrawObjs = ops.partialDrawObjs;

			/*
			 if ((this instanceof Kekule.Render.Ctab2DRenderer))
			 console.log(this.getClassName(), partialDrawObjs, !partialDrawObjs || this._isCurrChemObjNeedToBeDrawn(partialDrawObjs, context));
			 */

			if (partialDrawObjs && (!this._isCurrChemObjNeedToBeDrawn(partialDrawObjs, context)))
			{
				//console.log('no need partial draw', this.getClassName(), chemObj, partialDrawObjs);
				return null;
			}
			/*
			 else if (partialDrawObjs)
			 console.log('partial draw objects', this.getClassName(), partialDrawObjs);
			 */
			//p.options = ops;

			var renderOptionsGetter = (this.getRendererType() === Kekule.Render.RendererType.R3D) ?
					'getRender3DOptions' : 'getRenderOptions';
			var localOps = chemObj[renderOptionsGetter] ? chemObj[renderOptionsGetter]() : null;

			renderOptionsGetter = (this.getRendererType() === Kekule.Render.RendererType.R3D) ?
					'getOverriddenRender3DOptions' : 'getOverriddenRenderOptions';
			var localOverrideOps = chemObj[renderOptionsGetter] ? chemObj[renderOptionsGetter]() : null;

			ops = Kekule.Render.RenderOptionUtils.mergeRenderOptions(localOps || {}, ops);
			this.getRenderCache().options = ops;
			ops = Kekule.Render.RenderOptionUtils.mergeRenderOptions(localOverrideOps || {}, ops);
			//console.log('draw ops', this.getClassName(), localOps, ops);

			this.updateDrawInfoInCache(this.getChemObj(), context, baseCoord, options, ops);

			var isRoot = this.isRootRenderer();

			this.invokeEvent('prepareDrawing', {'context': context, 'obj': this.getChemObj()});

			//console.log('DRAW', isRoot);
			if (isRoot)
				this.beginDraw(context, baseCoord, ops);

			var result = this.doDraw(context, baseCoord, ops);
			this.getRenderCache(context).drawnElem = result;
			if (isRoot)
				this.endDraw(context, baseCoord, ops);

			this.invokeEvent('draw', {'context': context, 'obj': this.getChemObj()});
		}
		finally
		{
			this.__isDrawing = false;
		}

		return result;
	},
	/**
	 * Do actual work of {@link Kekule.Render.AbstractRenderer.draw}.
	 * @param {Object} context Context to be drawn, such as Canvas, SVG, VML and so on.
	 * @param {Hash} baseCoord Coord on context to draw the center of chemObj.
	 * @param {Hash} options Actual draw options, such as draw rectangle, draw style and so on.
	 * @returns {Object} Drawn element on context (such as SVG) or null on direct context (such as canvas).
	 * @private
	 */
	doDraw: function(context, baseCoord, options)
	{
		// do nothing here
		return this.doDrawSelf(context, baseCoord, options);
	},
	/**
	 * Do actual work of draw self object (without children).
	 * @param {Object} context Context to be drawn, such as Canvas, SVG, VML and so on.
	 * @param {Hash} baseCoord Coord on context to draw the center of chemObj.
	 * @param {Hash} options Actual draw options, such as draw rectangle, draw style and so on.
	 * @returns {Object} Drawn element on context (such as SVG) or null on direct context (such as canvas).
	 * @private
	 */
	doDrawSelf: function(context, baseCoord, options)
	{
		// do nothing here
		return null;
	},
	/**
	 * Redraw previous object on context with same draw options. Should not be called before draw.
	 * @param {Object} context
	 */
	redraw: function(context)
	{
		var isRoot = this.isRootRenderer();

		//console.log('[Redraw]', isRoot, this.getClassName(), this.getChemObj().getId? this.getChemObj().getId(): null);
		//console.log('REDRAW', this.getClassName(), isRoot);
		if (isRoot)
		{
			var cache = this.getRenderCache(context) || {};
			this.beginDraw(context, cache.baseCoord, cache.options);
		}
		var result = this.doRedraw(context);
		this.getRenderCache(context).drawnElem = result;
		if (isRoot)
			this.endDraw(context, cache.baseCoord, cache.options);
		return result;
	},
	/**
	 * Do actual work of {@link Kekule.Render.AbstractRenderer.redraw}. Descendants may override this method.
	 * @param {Object} context
	 * @private
	 */
	doRedraw: function(context)
	{
		// A default implementation. Descendants can override this to provide a more efficient one.
		var p = this.getRenderCache(context);
		//this.clear(context);
		//return this.doDraw(context, p.baseCoord, p.realDrawOptions);
		return this.draw(context, p.baseCoord, p.options);
	},

	/**
	 * Whether current renderer can modify elements drawn on context.
	 * @param {Object} context
	 * @returns {Bool}
	 */
	canModifyGraphic: function(context)
	{
		var b = this.getDrawBridge();
		return b.canModifyGraphic? b.canModifyGraphic(context): false;
	},

	/**
	 * Call this method before a series of rendered element updating job (for instance, call update method)
	 * to avoid unnecessary redraw.
	 */
	beginUpdateRenderer: function()
	{
		++this._suspendUpdateStatus;
	},
	/**
	 * Call this method after a series of rendered element updateing job,
	 * notify the renderer to redraw the context.
	 */
	endUpdateRenderer: function()
	{
		--this._suspendUpdateStatus;
		if (this._suspendUpdateStatus <= 0)
			this._suspendUpdateStatus = 0;
		if (!this.isUpdatingRenderer())
		{
			this.doEndUpdateRenderer();
		}
	},
	/** @private */
	doEndUpdateRenderer: function()
	{
		this.updateEx(this._suspendUpdateInfos);
		this._suspendUpdateInfos = [];
	},
	/**
	 * Check if beginUpdateRenderer is called and endUpdateRenderer is not called yet.
	 * @returns {Bool}
	 */
	isUpdatingRenderer: function()
	{
		return this._suspendUpdateStatus > 0;
	},

	/**
	 * Do a update job according to info provided by updateItems. Must be called after draw.
	 * @param {Array} updateInfos Each item has format: {context, items: [{updateType, updatedObjDetails: [{obj, propNames}]}]}
	 * @returns {Bool}
	 */
	updateEx: function(updateInfos)
	{
		var canModify;
		var result = true;
		if (!this.isUpdatingRenderer())
		{
			canModify = this.canModifyGraphic(context);
			if (canModify)
			{
				//result = this.doUpdate(context, updatedObjs, updateType);
				for (var i = 0, l = updateInfos.length; i < l; ++i)
				{
					var info = updateInfos[i];
					var context = info.context;
					for (var j = 0, k = info.items.length; j < k; ++j)
					{
						var item = info.items[j];
						result = result && this.doUpdate(context, item.updatedObjDetails, item.updateType);
						if (!result)  // update individual failed
							break;
					}
					if (!result)
						break;
				}
			}
			//if (!result)  // can not update by self, call parent or repaint the whole context
			else  // can not modify graphic, call parent to update the whole context
			{
				var isRoot = this.isRootRenderer();
				if (isRoot)
				{
					//console.log('update root', this.getClassName());
					var contexts = [];
					for (var i = 0, l = updateInfos.length; i < l; ++i)
					{
						var info = updateInfos[i];
						var context = info.context;
						Kekule.ArrayUtils.pushUnique(contexts, context);
					}
					for (var i = 0, l = contexts.length; i < l; ++i)
					{
						this.getDrawBridge().clearContext(context);
						var cache = this.getRenderCache(context);
						//console.log('draw root once', this.getClassName());
						this.draw(context, cache.baseCoord, cache.options);
					}
					return true;
				}
				else
				{
					var p = this.getParentRenderer();
					return p.updateEx(updateInfos);
				}
			}
			return result;
		}
		else // updating, suspend
		{
			if (!this._suspendUpdateInfos)
				this._suspendUpdateInfos = [];
			this._mergeRendererUpdateInfo(this._suspendUpdateInfos, updateInfos);
			return true;
		}
	},
	/**
	 * Merge src into dest
	 * @private
	 */
	_mergeRendererUpdateInfo: function(dest, src)
	{
		for (var i = 0, l = src.length; i < l; ++i)
		{
			var info = src[i];
			var index = this._indexOfContextInRendererUpdateInfos(info.context, dest);
			if (index < 0)
				dest.push(info);
			else
			{
				var old = dest[index];
				// TODO: updateType not yet merged
				old.items = old.items.concat(info.items);
			}
		}
		return dest;
	},
	/** @private */
	_indexOfContextInRendererUpdateInfos: function(context, infos)
	{
		for (var i = 0, l = infos.length; i < l; ++i)
		{
			var info = infos[i];
			if (info.context === context)
				return i;
		}
		return -1;
	},

	/**
	 * Update a child object inside chemObj. Must be called after draw.
	 * @param {Object} context
	 * @param {Variant} updatedObjDetails Object detail containing field {obj, propNames} or array of details.
	 * @param {Int} updateType Value from {@link Kekule.Render.ObjectUpdateType}
	 * @returns {Bool}
	 */
	update: function(context, updatedObjDetails, updateType)
	{
		/*
		var result = false;
		if (this.canModifyGraphic(context))
		{
			result = this.doUpdate(context, updatedObjs, updateType);
		}
		if (!result)  // can not update by self, call parent or repaint the whole context
		{
			if (this.isRootRenderer())
			{
				this.getDrawBridge().clearContext(context);
				var cache = this.getRenderCache(context);
				this.draw(context, cache.baseCoord, cache.options);
				return true;
			}
			else
			{
				var p = this.getParentRenderer();
				return p.update(context, updatedObjs, updateType);
			}
		}
		return result;
		*/
		var objDetails = [];
		for (var i = 0, l = updatedObjDetails.length; i < l; ++i)
		{
			var obj = updatedObjDetails[i].obj;
			if (this.isChemObjRenderedBySelf(context, obj))
				objDetails.push(updatedObjDetails[i]);
		}
		if (objDetails.length)
			return this.updateEx([{'context': context, items: [{'updateType': updateType, 'updatedObjDetails': objDetails/*updatedObjs*/}]}]);
		else
			return true;
	},
	/**
	 * Do actual work of update. Descendants may override this.
	 * @param {Object} context
	 * @param {Array} updatedObjDetails  Object detail containing field {obj, propNames} or array of details.
	 * @param {Int} updateType Value from {@link Kekule.Render.ObjectUpdateType}
	 * @returns {Bool}
	 * @private
	 */
	doUpdate: function(context, updateObjDetails, updateType)
	{
		//console.log('do update', this.getClassName(), updateObjDetails);
		return this.doUpdateSelf(context, updateObjDetails, updateType);
	},
	/**
	 * Do actual work of update self (without children). Descendants should override this.
	 * @param {Object} context
	 * @param {Array} updatedObjDetails  Object detail containing field {obj, propNames} or array of details.
	 * @param {Int} updateType Value from {@link Kekule.Render.ObjectUpdateType}
	 * @returns {Bool}
	 * @private
	 */
	doUpdateSelf: function(context, updatedObjDetails, updateType)
	{
		//console.log('[doUpdateSelf]', this.getClassName(), updatedObjDetails);
		var r = false;
		if (this.canModifyGraphic(context))
		{
			/*  // TODO: now has bugs, disable it
			    // work well now? 2014-06-06
			 */
			/*
			if (updatedObjs.indexOf(this.getChemObj()))
			{
				if (updateType === Kekule.Render.ObjectUpdateType.CLEAR)
					return this.doClear(context);
				else
				{
					//console.log('update by redraw', this.getClassName(), updatedObjs);
					// simpliest method to update is to redraw the whole chemObj
					this.doClear(context);
					var p = this.getRenderCache(context);
					return this.draw(context, p.baseCoord, p.options);
				}
			}
			*/
			var redrawSelf = false;
			var chemObj = this.getChemObj();
			for (var i = 0, l = updatedObjDetails.length; i < l; ++i)
			{
				var detail = updatedObjDetails[i];
				if (detail.obj === chemObj)
				{
					//console.log('update self detail', this.getClassName(), detail.obj.getId());
					redrawSelf = true;
					break;
				}
			}
			if (redrawSelf)
			{
				if (updateType === Kekule.Render.ObjectUpdateType.CLEAR)
					return this.doClear(context);
				else
				{
					//console.log('<update by redraw>', this.getClassName(), updatedObjDetails);
					// simpliest method to update is to redraw the whole chemObj
					this.doClear(context);
					var p = this.getRenderCache(context);
					return this.draw(context, p.baseCoord, p.options);
				}
			}
			/**/
			/*
			var T = Kekule.Render.ObjectUpdateType;
			switch (updateType)
			{
				case T.ADD:
					r = this.doAddNew(context, updatedObjs);
					break;
				case T.MODIFY:
					r = this.doModify(context, updatedObjs);
					break;
				case T.REMOVE:
					r = this.doRemove(context, updatedObjs);
					break;
				case T.CLEAR:
					r = this.doClear(context);
					this.invokeEvent('clear', {'context': context, 'obj': this.getChemObj()});
					break;
			}
			*/
		}
		return r;
	},

	/** @ignore */
	_extractObjsOfUpdateObjDetails: function(updatedObjDetails)
	{
		return Kekule.Render.UpdateObjUtils._extractObjsOfUpdateObjDetails(updatedObjDetails);
	},
	/** @ignore */
	_createUpdateObjDetailsFromObjs: function(updatedObjs)
	{
		return Kekule.Render.UpdateObjUtils._createUpdateObjDetailsFromObjs(updatedObjs);
	},

	/**
	 * Add a new child object to chemObj. Must be called after draw.
	 * @param {Object} context
	 * @param {Variant} updatedObjs
	 * @returns {Bool} Whether the actual add job is done.
	 * @private
	 */
	addNew: function(context, updatedObjs)
	{
		var details = this._createUpdateObjDetailsFromObjs(updatedObjs);
		return this.update(context, details, Kekule.Render.ObjectUpdateType.ADD);
	},
	/*
	 * Do actual work of addNew. Descendants should override this.
	 * This function should return true after actual work done. Otherwise false should be returned.
	 * @param {Object} context
	 * @param {Variant} updatedObjs
	 * @returns {Bool}
	 * @private
	 * @deprecated
	 */
	/*
	doAddNew: function(context, updatedObjs)
	{
		return false;
	},
	*/
	/**
	 * Modify chemObj or a child object inside chemObj. Must be called after draw.
	 * @param {Object} context
	 * @param {Variant} updatedObjs
	 * @returns {Bool} Whether the actual modify job is done.
	 * @private
	 */
	modify: function(context, updatedObjDetails)
	{
		return this.update(context, updatedObjDetails, Kekule.Render.ObjectUpdateType.MODIFY);
	},
	/*
	 * Do actual work of modify. Descendants should override this.
	 * This function should return true after actual work done. Otherwise false should be returned.
	 * @param {Object} context
	 * @param {Variant} updatedObjs
	 * @returns {Bool}
	 * @private
	 * @deprecated
	 */
	/*
	doModify: function(context, updatedObjs)
	{
		return false;
	},
	*/
	/**
	 * Remove a child object inside chemObj and update the rendering. Must be called after draw.
	 * @param {Object} context
	 * @param {Variant} removedObjs
	 * @returns {Bool} Whether the actual remove job is done.
	 * @private
	 */
	remove: function(context, removedObjs)
	{
		var details = this._createUpdateObjDetailsFromObjs(removedObjs);
		return this.update(context, details, Kekule.Render.ObjectUpdateType.REMOVE);
	},
	/*
	 * Do actual work of remove. Descendants should override this.
	 * This function should return true after actual work done. Otherwise false should be returned.
	 * @param {Object} context
	 * @param {Variant} removedObjs
	 * @returns {Bool}
	 * @private
	 * @deprecated
	 */
	/*
	doRemove: function(context, removedObjs)
	{
		return false;
	},
	*/

	/**
	 * Clear whole chemObj on context.
	 * @param {Object} context
	 * @returns {Bool} Whether the actual clear job is done.
	 */
	clear: function(context)
	{
		//console.log('[Clear]', this.getClassName(), this.getChemObj().getId? this.getChemObj().getId(): null);
		//return this.update(context, Kekule.Render.UpdateObjUtils._createUpdateObjDetailsFromObjs([this.getChemObj()]), Kekule.Render.ObjectUpdateType.CLEAR);
		var result = this.doClear(context);
		this.invokeEvent('clear', {'context': context, 'obj': this.getChemObj()});
	},
	/**
	 * Do actual job of clear.
	 * This function should return true after actual work done. Otherwise false should be returned.
	 * @param {Object} context
	 * @returns {Bool}
	 */
	doClear: function(context)
	{
		if (this.canModifyGraphic())
		{
			return this.doClearSelf(context);
		}
		else
			return false;
	},
	/**
	 * Do actual job of clear self (without children). Descendants should override this method.
	 * This function should return true after actual work done. Otherwise false should be returned.
	 * @param {Object} context
	 * @returns {Bool}
	 */
	doClearSelf: function(context)
	{
		if (this.canModifyGraphic())
		{
			//console.log('clear', this.getClassName());
			var drawnElem = this.getRenderCache(context).drawnElem;
			//console.log('clear', drawnElem);
			if (drawnElem)
			{
				try
				{
					this.getDrawBridge().removeDrawnElem(context, drawnElem);
				}
				catch(e)  // avoid error when drawnElem is already removed from context
				{
					//console.log('clear error', this.getClassName(), drawnElem);
				}
			}
			this.getRenderCache(context).drawnElem = null;
		}
		else
			return false;
	},
	/**
	 * Estimate the bound box around current chemObj (in chem coord system).
	 * @param {Object} context
	 * @param {Object} options
	 * @param {Bool} allowCoordBorrow
	 * @returns {Hash} A 2D or 3D box, in chemObj's coord system.
	 */
	estimateObjBox: function(context, options, allowCoordBorrow)
	{
		var box = this.doEstimateObjBox(context, options, allowCoordBorrow);
		//console.log('get box', this.getClassName(), box);
		// if box has some field which is undefined or null, set it to 0
		if (box)
			box = this._fillBoxDefaultValue(box, this.getRendererType());
		return box;
	},
	/**
	 * Do actual work of {@link Kekule.Render.AbstractRenderer.estimateObjBox}.
	 * @param {Object} context
	 * @param {Object} options
	 * @param {Bool} allowCoordBorrow
	 * @returns {Hash} A 2D or 3D box.
	 * @private
	 */
	doEstimateObjBox: function(context, options, allowCoordBorrow)
	{
		return this.doEstimateSelfObjBox(context, options, allowCoordBorrow);
	},
	/**
	 * Calculate the containing box of only this object (without children).
	 * Descendants may override this method.
	 * @param {Object} context
	 * @param {Object} options
	 * @param {Bool} allowCoordBorrow
	 * @returns {Hash} A 2D or 3D box.
	 * @private
	 */
	doEstimateSelfObjBox: function(context, options, allowCoordBorrow)
	{
		return null;
	},

	/**
	 * Estimate the bound box need to render current chemObj (in context coord system).
	 * Note: this method should not be called outside draw(). Otherwise the result may be unreliable or even no result can be returned.
	 * @param {Object} context
	 * @param {Hash} baseCoord Center coord in context to draw object. Can be null.
	 * @param {Object} options
	 * @param {Bool} allowCoordBorrow
	 * @returns {Hash} A 2D or 3D box, in context's coord system.
	 */
	estimateRenderBox: function(context, baseCoord, options, allowCoordBorrow)
	{
		var box = this.doEstimateRenderBox(context, baseCoord, options, allowCoordBorrow);
		// if box has some field which is undefined or null, set it to 0
		if (box)
			box = this._fillBoxDefaultValue(box, this.getRendererType());
		return box;
	},
	/**
	 * Do actual work of {@link Kekule.Render.AbstractRenderer.estimateRenderBox}.
	 * @param {Object} context
	 * @param {Hash} baseCoord Center coord in context to draw object. Can be null.
	 * @param {Object} options
	 * @param {Bool} allowCoordBorrow
	 * @returns {Hash} A 2D or 3D box.
	 * @private
	 */
	doEstimateRenderBox: function(context, baseCoord, options, allowCoordBorrow)
	{
		// do nothing here
		return null;
	},

	/** @private */
	_fillBoxDefaultValue: function(box, rendererType)
	{
		if (!box)
			box = {};
		var is3D = rendererType === Kekule.Render.RendererType.R3D;
		var r = {
			'x1': box.x1 || 0,
			'x2': box.x2 || 0,
			'y1': box.y1 || 0,
			'y2': box.y2 || 0
		};
		if (is3D)
		{
			r.z1 = box.z1 || 0;
			r.z2 = box.z2 || 0;
		}
		return r;
	},

	/**
	 * Transform a context based coord to inner coord basd on chemObj coord system.
	 * @param {Object} context
	 * @param {Kekule.ChemObject} chemObj
	 * @param {Hash} coord
	 * @returns {Hash}
	 */
	transformCoordToObj: function(context, chemObj, coord)
	{
		return this.doTransformCoordToObj(context, chemObj, coord);
	},
	/** @private */
	doTransformCoordToObj: function(context, chemObj, coord)
	{
		return coord;
	},
	/**
	 * Transform a chemObj based inner coord to context based one.
	 * @param {Object} context
	 * @param {Kekule.ChemObject} chemObj
	 * @param {Hash} coord
	 * @returns {Hash}
	 */
	transformCoordToContext: function(context, chemObj, coord)
	{
		return this.doTransformCoordToContext(context, chemObj, coord);
	},
	/** @private */
	doTransformCoordToContext: function(context, chemObj, coord)
	{
		return coord;
	},
	/**
	 * Transform a context based coord to screen based one (usually in pixel).
	 * @param {Object} context
	 * @param {Hash} coord
	 * @return {Hash}
	 */
	transformContextCoordToScreen: function(context, coord)
	{
		return this.doTransformContextCoordToScreen(context, coord);
	},
	/** @private */
	doTransformContextCoordToScreen: function(context, coord)
	{
		var b = this.getDrawBridge();
		return (b && b.transformContextCoordToScreen)? b.transformContextCoordToScreen(context, coord): coord;
	},

	/**
	 * Should be called when a basic object (node, connector, glyph...) is drawn on context.
	 * @param {Object} obj
	 * @param {Object} boundInfo
	 * @private
	 */
	basicDrawObjectUpdated: function(context, obj, parentObj, boundInfo, updateType)
	{
		/*
		if (!boundInfo)
			console.log(arguments.callee.caller.toString());
		*/
		this.invokeEvent('updateBasicDrawObject', {'context': context, 'obj': obj, 'parentObj': parentObj, 'boundInfo': boundInfo, 'updateType': updateType});
	},

	/**
	 * Indicate whether a chemObj (including childObj) is rendered by this renderer, or should be rendered by this renderer.
	 * Descendants may override this method.
	 * @param {Object} context
	 * @param {Object} obj
	 * @returns {boolean}
	 */
	isChemObjRenderedBySelf: function(context, obj)
	{
		return (obj === this.getRenderCache(context).chemObj);
	},
	/**
	 * Indicate whether a chemObj (including childObj) is rendered directly by this renderer (not by child renderers).
	 * Descendants may override this method.
	 * @param {Object} context
	 * @param {Object} obj
	 * @returns {boolean}
	 */
	isChemObjRenderedDirectlyBySelf: function(context, obj)
	{
		if (obj && this.getRenderCache(context).chemObj)
			return (obj === this.getRenderCache(context).chemObj);
		else
			return false;
	},

	/** @private */
	createBoundInfo: function(boundType, coords, additionalInfos)
	{
		return Kekule.Render.MetaShapeUtils.createShapeInfo(boundType, coords, additionalInfos);
	},
	/** @private */
	createPointBoundInfo: function(coord)
	{
		return this.createBoundInfo(Kekule.Render.BoundShapeType.POINT, [coord]);
	},
	/** @private */
	createCircleBoundInfo: function(coord, radius)
	{
		return this.createBoundInfo(Kekule.Render.BoundShapeType.CIRCLE, [coord], {'radius': radius});
	},
	/** @private */
	createArcBoundInfo: function(coord, radius, startAngle, endAngle, anticlockwise, width)
	{
		return this.createBoundInfo(Kekule.Render.BoundShapeType.ARC, [coord],
				{'radius': radius, 'startAngle': startAngle, 'endAngle': endAngle, 'anticlockwise': anticlockwise, 'width': width});
	},
	/** @private */
	createLineBoundInfo: function(coord1, coord2, width)
	{
		return this.createBoundInfo(Kekule.Render.BoundShapeType.LINE, [coord1, coord2], {'width': width});
	},
	/** @private */
	createRectBoundInfo: function(coord1, coord2)
	{
		return this.createBoundInfo(Kekule.Render.BoundShapeType.RECT, [coord1, coord2]);
	},
	/** @private */
	createSphereBoundInfo: function(coord, radius)
	{
		return this.createBoundInfo(Kekule.Render.BoundShapeType.SPHERE, [coord], {'radius': radius});
	},
	/** @private */
	createCylinderBoundInfo: function(coord1, coord2, radius)
	{
		return this.createBoundInfo(Kekule.Render.BoundShapeType.CYLINDER, [coord1, coord2], {'radius': radius});
	}
});
Kekule.ClassDefineUtils.addExtraObjMapSupport(Kekule.Render.AbstractRenderer);

/**
 * A dummy renderer that does nothing.
 * @class
 * @augments Kekule.Render.AbstractRenderer
 */
Kekule.Render.DummyRenderer = Class.create(Kekule.Render.AbstractRenderer,
/** @lends Kekule.Render.DummyRenderer# */
{
	/** @private */
	CLASS_NAME: 'Kekule.Render.DummyRenderer',
	/** @ignore */
	draw: function(context, baseCoord, options)
	{
		return;
	},
	/** @ignore */
	redraw: function(context)
	{
		return;
	}
});

/**
 * A base renderer class to draw object togather with its children.
 * @class
 * @augments Kekule.Render.AbstractRenderer
 */
Kekule.Render.CompositeRenderer = Class.create(Kekule.Render.AbstractRenderer,
/** @lends Kekule.Render.CompositeRenderer# */
{
	/** @private */
	CLASS_NAME: 'Kekule.Render.CompositeRenderer',
	/** @private */
	initProperties: function()
	{
		this.defineProp('targetChildObjs', {
			'dataType': DataType.ARRAY,
			'serializable': false
		});
		this.defineProp('childRendererMap', {
			'dataType': DataType.OBJECT,
			'serializable': false,
			'getter': function()
			{
				var result = this.getPropStoreFieldValue('childRendererMap');
				if (!result)
				{
					result = new Kekule.MapEx(true);  // non-weak map, as we should store the renderers
					this.setPropStoreFieldValue('childRendererMap', result);
				}
				return result;
			}
		});
	},
	/** @ignore */
	finalize: function($super)
	{
		this.reset();
		$super();
	},

	/** @ignore */
	doEstimateObjBox: function($super, context, options, allowCoordBorrow)
	{
		var result = $super(context, options, allowCoordBorrow);
		var renderers = this.prepareChildRenderers();
		var BU = Kekule.BoxUtils;
		for (var i = 0, l = renderers.length; i < l; ++i)
		{
			var r = renderers[i];
			if (r)
			{
				var b = r.estimateObjBox(context, options, allowCoordBorrow);
				if (b)
				{
					if (!result)
						result = BU.clone(b); //Object.extend({}, b);
					else
						result = BU.getContainerBox(result, b);
				}
			}
		}
		return result;
	},

	/** @ignore */
	isChemObjRenderedBySelf: function($super, context, obj)
	{
		var result = $super(context, obj);
		//console.log('check rendered by self', obj.getClassName(), this.getClassName(), result);
		if (!result)
		{
			var childRenderers = this.getChildRenderers();
			for (var i = 0, l = childRenderers.length; i < l; ++i)
			{
				var r = childRenderers[i];
				if (r.isChemObjRenderedBySelf(context, obj))
					return true;
			}
		}
		if (!result)
		{
			this.refreshChildObjs();
			var objs = this.getTargetChildObjs();
			result = (objs && objs.indexOf(obj) >= 0);
			//console.log('here', this.getClassName(), obj.getClassName(), result);
		}
		return result;
	},
	/** @ignore */
	isChemObjRenderedDirectlyBySelf: function($super, context, obj)
	{
		return $super(context, obj);
	},
	/** @ignore */
	doSetRedirectContext: function($super, value)
	{
		$super(value);
		// if has child renderers, set redirect context as well
		var childRenderers = this.getChildRenderers();
		if (childRenderers && childRenderers.length)
		{
			for (var i = 0, l = childRenderers.length; i < l; ++i)
			{
				childRenderers[i].setRedirectContext(value);
			}
		}
	},


	/**
	 * Returns all children of this.getChemObj(). Descendants must override this method.
	 * If no children is found, null should be returned.
	 * @returns {Array}
	 * @private
	 */
	getChildObjs: function()
	{
		var chemObj = this.getChemObj();
		if (chemObj && chemObj.getAttachedMarkers)
			return [].concat(chemObj.getAttachedMarkers() || []);
		else
			return [];
	},
	/**
	 * Prepare all child objects to be drawn.
	 * @private
	 */
	prepareChildObjs: function()
	{
		var childObjs = this.getTargetChildObjs();
		if (childObjs)  // already prepared
			return childObjs;

		this.setTargetChildObjs(this.getChildObjs());
		return this.getTargetChildObjs();
	},
	/** @private */
	refreshChildObjs: function()
	{
		this.setTargetChildObjs(null);
		this.prepareChildObjs();
		/*
		if (this.getTargetChildObjs().length)
			console.log('refresh child', this.getClassName(), this.getTargetChildObjs());
		*/
	},
	/** @private */
	getChildRenderers: function()
	{
		return this.getChildRendererMap().getValues();
	},
	/** @private */
	getRendererForChild: function(childObj, canCreate)
	{
		var renderSelector = (this.getRendererType() === Kekule.Render.RendererType.R3D)?
				Kekule.Render.get3DRendererClass: Kekule.Render.get2DRendererClass;
		var rendererMap = this.getChildRendererMap();
		var result = rendererMap.get(childObj);
		if (!result && canCreate)
		{
			var c = renderSelector(childObj) || Kekule.Render.DummyRenderer;  // dummy renderer, do nothing
			var result = c? new c(childObj, this.getDrawBridge(), /*this.getRenderConfigs(),*/ this): null;  // renderer may be null for some unregistered objects
			rendererMap.set(childObj, result);
			result.setRedirectContext(this.getRedirectContext());
		}
		return result;
	},
	/**
	 * Prepare renders to draw child objects.
	 * @private
	 */
	prepareChildRenderers: function()
	{
		var rendererMap = this.getChildRendererMap();

		var childObjs = this.prepareChildObjs() || [];

		// remove unneed renderers
		var oldRenderedObjs = rendererMap.getKeys();
		for (var i = 0, l = oldRenderedObjs.length; i < l; ++i)
		{
			var obj = oldRenderedObjs[i];
			if (childObjs.indexOf(obj) < 0)
				rendererMap.remove(obj);
		}
		// add new renderers if needed
		for (var i = 0, l = childObjs.length; i < l; ++i)
		{
			var childObj = childObjs[i];
			this.getRendererForChild(childObj, true);
		}

		//return childRenderers;
		return rendererMap.getValues();
	},
	/**
	 * Release all child renderer instance.
	 * @private
	 */
	releaseChildRenderers: function()
	{
		var rendererMap = this.getChildRendererMap();
		var childRenderers = rendererMap.getValues();
		if (!childRenderers)
			return;
		for (var i = 0, l = childRenderers.length; i < l; ++i)
		{
			childRenderers[i].finalize();
		}
		rendererMap.clear();
	},
	/** @private */
	hasChildRenderers: function()
	{
		var childRenderers = this.getChildRendererMap().getValues();
		var result = childRenderers && childRenderers.length;
		return result;
	},

	/**
	 * Prepare child objects and renderers, a must have step before draw.
	 * @private
	 */
	prepare: function()
	{
		this.prepareChildObjs();
		this.prepareChildRenderers();
	},
	/**
	 * Set renderer to initialized state, clear childObjs and childRenderers.
	 * @private
	 */
	reset: function()
	{
		//console.log('reset', this.getClassName());
		this.setTargetChildObjs(null);
		this.releaseChildRenderers();
	},

	/**
	 * Whether the whole renderer (and its children) should be wholely repainted even in partial draw mode.
	 * Descendants may override this.
	 * @returns {Bool}
	 * @private
	 */
	_needWholelyDraw: function(partialDrawObjs, context)
	{
		var selfObj = this.getChemObj();
		return !partialDrawObjs || partialDrawObjs.indexOf(selfObj) >= 0;
	},

	/** @private */
	doDraw: function($super, context, baseCoord, options)
	{
		//this.reset();
		/*
		this.setTargetChildObjs(null);  // refresh child objects first
		this.prepare();
		//console.log('draw', this.getClassName(), options.partialDrawObjs, baseCoord);
		*/
		this.refreshChildObjs();  // refresh child objects first
		this.prepareChildRenderers();  // refresh renderer list

		var op = Object.create(options);
		if (options.partialDrawObjs && this._needWholelyDraw(options.partialDrawObjs, context))
			op.partialDrawObjs = null;  // if self need to be draw, all child renderers should be repainted as well

		//if (!this.hasChildRenderers())
		if (!this.getTargetChildObjs().length)
			return $super(context, baseCoord, op);
		else  // then draw each child objects by child renderers
		{
			//console.log('do draw self', this.getClassName());
			var selfElem = this.doDrawSelf(context, baseCoord, op);
			var group = this.doDrawChildren(context, baseCoord, op);
			// self
			if (selfElem)
				this.addToDrawGroup(selfElem, group);
			return group;
		}
	},
	/** @private */
	doDrawChildren: function(context, baseCoord, options)
	{
		var group = this.createDrawGroup(context);
		var childRenderers = this.getChildRenderers();

		var ops = Object.create(options);

		this.getRenderCache(context).childDrawOptions = ops;

		for (var i = 0, l = childRenderers.length; i < l; ++i)
		{
			var r = childRenderers[i];
			var baseCoord = null;
			var elem = r.draw(context, baseCoord, ops);
			if (group && elem)
				this.addToDrawGroup(elem, group);
		}
		//console.log('draw children', this.getClassName(), group, childRenderers.length, this.getTargetChildObjs());
		return group;
	},
	/** @private */
	doClear: function($super, context)
	{
		$super(context);
		if (this.hasChildRenderers())
		{
			this.doClearChildren(context);
		}
		return true;
	},
	/** @private */
	doClearChildren: function(context)
	{
		var childRenderers = this.getChildRendererMap().getValues();
		for (var i = 0, l = childRenderers.length; i < l; ++i)
		{
			if (childRenderers[i])
			{
				childRenderers[i].clear(context);
			}
		}
	},
	/** @private */
	doUpdate: function($super, context, updateObjDetails, updateType)
	{
		this.refreshChildObjs();  // refresh child objects first
		//this.prepare();
		// update self
		$super(context, updateObjDetails, updateType);
		//if (this.hasChildRenderers())
		if (this.getTargetChildObjs().length)
		{
			//console.log('do update children of ', this.getClassName());
			this.doUpdateChildren(context, updateObjDetails, updateType);
		}
		return true;
	},
	/** @private */
	doUpdateChildren: function(context, updateObjDetails, updateType)
	{
		var updatedObjs = Kekule.Render.UpdateObjUtils._extractObjsOfUpdateObjDetails(updateObjDetails);

		//console.log('update Objs', this.getClassName(), updatedObjs);

		var directChildren = this.getTargetChildObjs() || [];
		var childRendererMap = this.getChildRendererMap();
		var objs = Kekule.ArrayUtils.toArray(updatedObjs);
		var objsMap = new Kekule.MapEx(false);
		var renderers = [];
		var redrawRoot = false;

		for (var i = 0, l = objs.length; i < l; ++i)
		{
			var obj = objs[i];
			if (this.isChemObjRenderedDirectlyBySelf(context, obj))  // need redraw self
			{
				redrawRoot = true;
			}
		}
		if (redrawRoot)
		{
			this.doClear(context);
			this.redraw(context);
			return true;
		}

		for (var i = 0, l = objs.length; i < l; ++i)
		{
			var obj = objs[i];
			/*  // TODO: now has bugs, disable it currently
			 if (this.isChemObjRenderedDirectlyBySelf(context, obj))  // the root object it self updated, need re-render self
			 {
			 //console.log('do redraw');
			 redrawRoot = true;
			 //return true;
			 }
			 */

			var renderer = childRendererMap.get(obj);

			if (renderer)  // is direct child and has a corresponding renderer
			{
				// check update type, if updateType is remove, just remove the renderer
				if (updateType === Kekule.Render.ObjectUpdateType.REMOVE)
				{
					renderer.clear();
					childRendererMap.remove(obj);
				}
				else
				{
					Kekule.ArrayUtils.pushUnique(renderers, renderer);
					olds = [obj];
					objsMap.set(renderer, olds);
				}
			}
			else
			{
				var rs = this._getRenderersForChildObj(context, obj);
				if (!rs.length)
				{
					if (directChildren.indexOf(obj) >= 0)  // still can not find
					{
						var r = this.getRendererForChild(obj, true);

						var drawnResult = r.draw(context, null, this.getRenderCache(context).childDrawOptions);
						if (drawnResult)
						{
							var drawnElem = this.getCachedDrawnElem(context);
							if (drawnElem)
								this.addToDrawGroup(drawnResult, drawnElem);
						}
					}
				}
				for (var j = 0, k = rs.length; j < k; ++j)
				{
					var renderer = rs[j];
					var olds = objsMap.get(renderer);
					if (!olds)
					{
						renderers.push(renderer);
						olds = [];
						objsMap.set(renderer, olds);
					}
					olds.push(obj);
				}
			}
		}

		// apply update in each renderer
		var result = true;
		for (var i = 0, l = renderers.length; i < l; ++i)
		{
			var renderer = renderers[i];
			var o = objsMap.get(renderer);
			var details = Kekule.Render.UpdateObjUtils._createUpdateObjDetailsFromObjs(o);
			//console.log('child renderer update', renderer.getClassName(), details);
			var r = renderer.update(context, details, updateType);
			result = result && r;
		}
		return result;
	},

	/** @private */
	_getRenderersForChildObj: function(context, childObj)
	{
		var result = [];
		var childRenderers = this.getChildRenderers();
		for (var i = 0, l = childRenderers.length; i < l; ++i)
		{
			var r = childRenderers[i];
			if (r.isChemObjRenderedBySelf(context, childObj))
				result.push(r);
		}
		return result;
	}
});


/**
 * 2D renderer factory.
 * @class
 */
Kekule.Render.Renderer2DFactory = Kekule.FactoryUtils.createSimpleFactory(Kekule.FactoryUtils.MATCH_BY_CLASS);
/**
 * Returns a suitable 2D renderer class for chemObj
 * @param {Object} chemObj
 * @returns {Kekule.Render.AbstractRenderer}
 * @function
 */
Kekule.Render.get2DRendererClass = function(chemObj)
{
	var aClass = (chemObj instanceof ObjectEx)? chemObj.getClass(): chemObj;
	return Kekule.Render.Renderer2DFactory.getClass(aClass);
};
/**
 * 3D renderer factory.
 * @class
 */
Kekule.Render.Renderer3DFactory = Kekule.FactoryUtils.createSimpleFactory(Kekule.FactoryUtils.MATCH_BY_CLASS);
/**
 * Returns a suitable 3D renderer class for chemObj
 * @param {Object} chemObj
 * @returns {Kekule.Render.AbstractRenderer}
 * @function
 */
Kekule.Render.get3DRendererClass = function(chemObj)
{
	var aClass = (chemObj instanceof ObjectEx)? chemObj.getClass(): chemObj;
	return Kekule.Render.Renderer3DFactory.getClass(aClass);
};


/**
 * Implemtation of 2D/3D draw bridge manager.
 * @class
 * @ignore
 */
Kekule.Render.DrawBridgeManager = Class.create({
	/** @private */
	CLASS_NAME: 'Kekule.Render.DrawBridgeManager',
	/** @ignore */
	initialize: function()
	{
		this._items = [];
		this._preferredItem = null;
	},
	/** @private */
	_indexOfBridgeClass: function(bridgeClass)
	{
		for (var i = 0, l = this._items.length; i < l; ++i)
		{
			var item = this._items[i];
			if (item.bridgeClass === bridgeClass)
				return i;
		}
		return -1;
	},
	/** @private */
	_sortItems: function()
	{
		this._items.sort(
			function(item1, item2)
			{
				return (item1.priorityLevel || 0) - (item2.priorityLevel || 0);
			}
		)
	},
	/** @private */
	_reselectPreferred: function()
	{
		this._sortItems();
		for (var i = this._items.length - 1; i >= 0; --i)
		{
			var item = this._items[i];
			if (item.isSupported)
			{
				this._preferredItem = item;
				return item;
			}
		}
		this._preferredItem = null;
		return null;
	},

	/**
	 * Register a bridge.
	 * @param {Class} bridgeClass
	 * @param {Int} priorityLevel
	 * @returns {Object}
	 * @ignore
	 */
	register: function(bridgeClass, priorityLevel)
	{
		if (!priorityLevel)
			priorityLevel = 0;
		var index = this._indexOfBridgeClass(bridgeClass);
		var item;
		if (index >= 0)
		{
			item = this._items[index];
			item.priorityLevel = priorityLevel;
		}
		else
		{
			item = {'bridgeClass': bridgeClass, 'priorityLevel': priorityLevel};
			item.isSupported = bridgeClass.isSupported? bridgeClass.isSupported(): false;
			this._items.push(item);
		}
		this._sortItems();

		if ((!this._preferredItem) || (this._preferredItem.priorityLevel < priorityLevel))
		{
			if (item.isSupported)  // if isSupported method not exists, assure it always not be supported
				this._preferredItem = item;
		}

		return item;
	},

	/**
	 * Unregister a bridge.
	 * @param {Class} bridgeClass
	 * @returns {Object}
	 * @ignore
	 */
	unregister: function(bridgeClass)
	{
		var item = null;
		var i = this._indexOfBridgeClass(bridgeClass);
		if (i >= 0)
		{
			item = this._items[i];
			this._items.splice(i, 1);
			if (item === this._preferredItem)
				this._reselectPreferred();
		}
		return item;
	},

	/**
	 * Gets most suitable bridge class in current environment.
	 * @returns {Class}
	 * @ignore
	 */
	getPreferredBridgeClass: function()
	{
		return (this._preferredItem)? this._preferredItem.bridgeClass: null;
	},
	/**
	 * Returns instance of preferred bridge in current environment.
	 * @returns {Object}
	 * @ignore
	 */
	getPreferredBridgeInstance: function()
	{
		var c = this.getPreferredBridgeClass();
		if (c)
		{
			/*
			if (!c.getInstance)  // class has not been singletoned
				Kekule.ClassUtils.makeSingleton(c);
			return c.getInstance()
			*/
			return new c();
		}
		else
			return null;
	}
});

/**
 * Draw bridge manager for 2D rendering.
 * @object
 */
Kekule.Render.DrawBridge2DMananger = new Kekule.Render.DrawBridgeManager();
/**
 * Draw bridge manager for 3D rendering.
 * @object
 */
Kekule.Render.DrawBridge3DMananger = new Kekule.Render.DrawBridgeManager();