kekule
Version:
Open source JavaScript toolkit for chemoinformatics
897 lines (827 loc) • 34.2 kB
JavaScript
/**
* @fileoverview
* A default implementation of 2D renderer to render small glyphs.
* @author Partridge Jiang
*/
/*
* requires /lan/classes.js
* requires /utils/kekule.utils.js
* requires /render/2d/kekule.render.render2D.js
*/
(function()
{
/** @ignore */
var PU = Kekule.Render.DrawPathUtils;
var CU = Kekule.CoordUtils;
var oneOf = Kekule.oneOf;
var NT = Kekule.Glyph.NodeType;
var PT = Kekule.Glyph.PathType;
/**
* Class to render a of small glyph.
* @class
* @augments Kekule.Render.ChemObj2DRenderer
*/
Kekule.Render.BaseGlyph2DRenderer = Class.create(Kekule.Render.ChemObj2DRenderer,
/** @lends Kekule.Render.BaseGlyph2DRenderer# */
{
/** @private */
CLASS_NAME: 'Kekule.Render.BaseGlyph2DRenderer',
/** @private */
doDraw: function(/*$super, */context, baseCoord, options)
{
var ops = Object.create(options);
ops.strokeColor = options.strokeColor || options.glyphStrokeColor;
ops.fillColor = options.fillColor || options.glyphFillColor;
ops.strokeWidth = options.strokeWidth || options.glyphStrokeWidth;
ops.lineCap = options.glyphLineCap;
ops.lineJoin = options.glyphLineJoin;
return this.tryApplySuper('doDraw', [context, baseCoord, ops]) /* $super(context, baseCoord, ops) */;
}
});
/**
* A default implementation of 2D a molecule's CTab renderer.
* @class
* @augments Kekule.Render.Ctab2DRenderer
*/
Kekule.Render.PathGlyphCtab2DRenderer = Class.create(Kekule.Render.Ctab2DRenderer,
/** @lends Kekule.Render.PathGlyphCtab2DRenderer# */
{
/** @private */
CLASS_NAME: 'Kekule.Render.PathGlyphCtab2DRenderer',
/** @constructs */
initialize: function(/*$super, */chemObj, drawBridge, parent)
{
this.tryApplySuper('initialize', [chemObj, drawBridge, parent]) /* $super(chemObj, drawBridge, parent) */;
this._nodeCoordOverrideMap = new Kekule.MapEx(true); // non-weak, for clear call
},
/** @ignore */
doFinalize: function(/*$super*/)
{
this._nodeCoordOverrideMap.finalize();
this.tryApplySuper('doFinalize') /* $super() */;
},
/** @private */
extractGlyphDrawOptions: function(renderOptions)
{
var unitLength = renderOptions.unitLength || 1;
return {
'strokeColor': renderOptions.strokeColor || renderOptions.color || renderOptions.glyphStrokeColor,
'fillColor': renderOptions.fillColor || renderOptions.color || renderOptions.glyphFillColor,
'strokeWidth': (renderOptions.strokeWidth || renderOptions.glyphStrokeWidth) * unitLength,
'lineCap': renderOptions.lineCap || renderOptions.glyphLineCap,
'lineJoin': renderOptions.lineJoin || renderOptions.glyphLineJoin
}
},
/** @private */
doDraw: function(/*$super, */context, baseCoord, options)
{
//console.log('do draw ctab');
var result = this.tryApplySuper('doDraw', [context, baseCoord, options]) /* $super(context, baseCoord, options) */;
this._nodeCoordOverrideMap.clear(); // clear override map after a full draw
return result;
},
/** @private */
doDrawNode: function(context, group, node, parentChemObj, options, finalTransformOptions)
{
var boundInfo;
var nodeType = node.getNodeType();
if (!nodeType || nodeType === NT.LOCATION || nodeType === NT.CONTROLLER) // no need to draw, but add bound info
{
var overrideCoord = this._nodeCoordOverrideMap.get(node); // node coord maybe overrided by connector drawing for offset bounds
var coord = overrideCoord || this.getTransformedCoord2D(context, node, finalTransformOptions.allowCoordBorrow);
boundInfo = this.createPointBoundInfo(coord);
}
if (boundInfo)
{
this.basicDrawObjectUpdated(context, node, parentChemObj, boundInfo, Kekule.Render.ObjectUpdateType.ADD);
}
},
/** @private */
doDrawConnector: function(/*$super, */context, group, connector, parentChemObj, options, finalTransformOptions)
{
var result = this.tryApplySuper('doDrawConnector', [context, group, connector, parentChemObj, options, finalTransformOptions]) /* $super(context, group, connector, parentChemObj, options, finalTransformOptions) */;
// if connector has control point, add bound info
var controlPoints = connector.getControlPoints && connector.getControlPoints();
if (controlPoints)
{
for (var i = 0, l = controlPoints.length; i < l; ++i)
{
var coord = this.getTransformedCoord2D(context, controlPoints[i], finalTransformOptions.allowCoordBorrow);
//console.log('transformed control point coord', coord);
var boundInfo = this.createPointBoundInfo(coord);
this.basicDrawObjectUpdated(context, controlPoints[i], connector, boundInfo, Kekule.Render.ObjectUpdateType.ADD);
}
}
return result;
},
/** @private */
doDrawConnectorShape: function(context, connector, nodes, renderOptions, finalTransformOptions)
{
/*
var coord1 = Object.extend({}, this.getTransformedCoord2D(context, node1, finalTransformOptions.allowCoordBorrow));
var coord2 = Object.extend({}, this.getTransformedCoord2D(context, node2, finalTransformOptions.allowCoordBorrow));
*/
var AS = Kekule.Glyph.ArrowSide;
var node1 = nodes[0];
var node2 = nodes[1];
var coord1 = CU.clone(this.getTransformedCoord2D(context, node1, finalTransformOptions.allowCoordBorrow));
var coord2 = CU.clone(this.getTransformedCoord2D(context, node2, finalTransformOptions.allowCoordBorrow));
var drawOptions = this.extractGlyphDrawOptions(renderOptions);
var pathType = connector.getPathType();
var pathParams = connector.getPathParams() || {};
var ctab = this.getChemObj();
// prepare draw parell paths
var lineGap = (pathParams.lineGap || 0) * renderOptions.defScaleRefLength;
var lineCount = lineGap? (pathParams.lineCount || 1): 1;
//var isEven = lineCount & 1; // even or order number
//if ((lineCount > 1) && lineGap)
/*
var coordDelta = CU.substract(coord2, coord1);
var w = coordDelta.x;
var h = coordDelta.y;
var l = CU.getDistance(coord1, coord2);
var angleSin = h / l;
var angleCos = w / l;
var deltaObjVector = {'y': lineGap * angleCos, 'x': lineGap * angleSin};
var deltaScreenCoord = CU.substract(
this.transformCoordToContext(context, ctab, {'x': 0, 'y': 0}),
this.transformCoordToContext(context, ctab, deltaObjVector)
);
var lineScreenGap = CU.getDistance(deltaScreenCoord, {'x': 0, 'y': 0});
var totalDeltaScreenCoord = CU.multiply(deltaScreenCoord, (lineCount - 1) / 2);
*/
// prepare draw arrows
var startArrowParams, endArrowParams;
var arrowElems = [];
if (!!pathParams.startArrowType && pathParams.startArrowWidth)
{
var transedLengthes = this._transformArrowLength(context, pathParams.startArrowLength, pathParams.startArrowWidth);
startArrowParams = {
arrowLength: transedLengthes.arrowLength,
arrowWidth: transedLengthes.arrowWidth,
arrowType: pathParams.startArrowType,
arrowSide: pathParams.startArrowSide,
drawOnCenter: (!pathParams.startArrowSide || pathParams.startArrowSide === AS.BOTH)
//arrowOffset: pathParams.startArrowOffset
};
}
if (!!pathParams.endArrowType && pathParams.endArrowWidth)
{
var transedLengthes = this._transformArrowLength(context, pathParams.endArrowLength, pathParams.endArrowWidth);
var endArrowParams = {
arrowLength: transedLengthes.arrowLength,
arrowWidth: transedLengthes.arrowWidth,
arrowType: pathParams.endArrowType,
arrowSide: pathParams.endArrowSide,
//arrowOffset: pathParams.endArrowOffset
drawOnCenter: (!pathParams.endArrowSide || pathParams.endArrowSide === AS.BOTH)
};
}
// calculate the autoOffset positions
var offsetBounds = [];
var inflateShape = Kekule.Render.MetaShapeUtils.inflateShape;
for (var i = 0, l = nodes.length; i < l; ++i)
{
var nodeParams = nodes[i].getPathNodeParams();
if (nodeParams.useStickingOffset)
{
var offsetBound = this.getStickingTargetRenderBound(context, nodes[i]);
if (offsetBound)
{
var offsetRelLength = nodeParams.stickingOffsetRelLength;
if (Kekule.ObjUtils.isUnset(offsetRelLength))
{
offsetRelLength = renderOptions.glyphStickOffsetRelLength;
}
if (offsetRelLength)
{
var offsetContextLength = this._doGetStickOffsetContextLength(context, offsetRelLength, renderOptions);
offsetBound = inflateShape(offsetBound, offsetContextLength);
}
offsetBounds[i] = offsetBound;
}
}
}
/*
var offsetBound1, offsetBound2;
if (pathParams.autoOffset)
{
offsetBound1 = this.getStickingTargetRenderBound(context, node1);
offsetBound2 = this.getStickingTargetRenderBound(context, node2);
//console.log(connector.getClassName(), 'autoOffset', offsetBound1, offsetBound2);
// calculate the glyphStickOffsetRefLength, do the offset
}
//var offsetBounds = [offsetBound1, offsetBound2];
if (offsetBound1 || offsetBound2)
{
//console.log(renderOptions);
var offsetRelLength = pathParams.glyphStickOffsetRelLength || renderOptions.glyphStickOffsetRelLength;
var offsetContextLength = offsetRelLength? this._doGetStickOffsetContextLength(context, offsetRelLength, renderOptions): null;
if (offsetContextLength)
{
var inflateShape = Kekule.Render.MetaShapeUtils.inflateShape;
for (var i = 0, l = offsetBounds.length; i < l; ++i)
{
if (offsetBounds[i])
{
//console.log('before inflate', offsetBounds[i], offsetContextLength);
offsetBounds[i] = inflateShape(offsetBounds[i], offsetContextLength);
//console.log('after inflate', offsetBounds[i]);
}
}
}
}
*/
// draw parrel lines
var drawnElems = [];
var boundInfos = [];
/*
var distance = l;
var midNo = (lineCount - 1) / 2;
*/
var deltaObjVector = {'y': 0, 'x': lineGap};
var deltaScreenCoord = CU.substract(
this.transformCoordToContext(context, ctab, {'x': 0, 'y': 0}),
this.transformCoordToContext(context, ctab, deltaObjVector)
);
var lineScreenGap = CU.getDistance(deltaScreenCoord, {'x': 0, 'y': 0});
if (pathType === PT.LINE)
{
var lineResult = this._doDrawLineConnectorShape(context, ctab, connector, nodes, coord1, coord2, lineCount, lineScreenGap, startArrowParams, endArrowParams, offsetBounds, drawOptions, renderOptions);
drawnElems = lineResult.drawnElems;
arrowElems = lineResult.arrowElems;
boundInfos = lineResult.boundInfos;
}
else if (pathType === PT.ARC)
{
var controlPoints = connector.getControlPoints && connector.getControlPoints();
var controlPoint = controlPoints && controlPoints[0];
if (controlPoint) // can drawn only when has control point
{
var coordController = CU.clone(this.getTransformedCoord2D(context, controlPoint, finalTransformOptions.allowCoordBorrow));
var lineResult = this._doDrawArcConnectorShape(context, ctab, connector, nodes, coord1, coord2, coordController, lineCount, lineScreenGap, startArrowParams, endArrowParams, offsetBounds, drawOptions, renderOptions);
drawnElems = lineResult.drawnElems;
arrowElems = lineResult.arrowElems;
boundInfos = lineResult.boundInfos;
}
}
if (!boundInfos.length)
boundInfos = null;
if ((lineCount <= 1) && !arrowElems.length)
return {element: drawnElems && drawnElems[0], boundInfo: boundInfos && boundInfos[0]};
else
{
var drawnGroup = this.createDrawGroup(context);
for (var i = 0; i < lineCount; ++i)
{
if (drawnElems[i])
this.addToDrawGroup(drawnElems[i], drawnGroup);
}
for (var i = 0, l = arrowElems.length; i < l; ++i)
{
if (arrowElems[i])
this.addToDrawGroup(arrowElems[i], drawnGroup);
}
return {element: drawnGroup, boundInfo: boundInfos};
}
},
/** @private */
_doGetStickOffsetContextLength: function(context, offsetRelLength, renderOptions)
{
var objLength = offsetRelLength * renderOptions.medianObjRefLength;
var coord0 = {x: 0, y: 0};
var coord1 = {x: objLength, y: 0};
var renderer = this.getRootRenderer();
var contextCoord0 = renderer.transformCoordToContext(context, this.getChemObj(), coord0);
var contextCoord1 = renderer.transformCoordToContext(context, this.getChemObj(), coord1);
return CU.getDistance(contextCoord0, contextCoord1)
},
/* @private */
/*
_doCalcBoundInterectPointToLinePath: function(bound, coord1, coord2)
{
},
*/
/** @private */
_doDrawLineConnectorShape: function(context, ctab, connector, nodes, coord1, coord2, lineCount, lineGap, startArrowParams, endArrowParams, offsetBounds, shapeDrawOptions, otherRenderOptions)
{
// TODO: currently a very rough approach, only support rect bound (for drawing line to atom label)
var actualEndCoords = [coord1, coord2];
var testVector = [coord1, coord2];
var midCoord = CU.divide(CU.add(coord1, coord2), 2);
for (var i = 0; i < 2; ++i)
{
var offsetBound = offsetBounds[i];
if (offsetBound)
{
var crossPoints = Kekule.Render.MetaShapeUtils.getCrossPointsOfVectorToShapeEdges(testVector, offsetBound, true); // shortcut when find the first cross point
//console.log('line cross', crossPoints);
if (crossPoints && crossPoints.length) // we should draw line to this point rather than the original end point
{
//actualEndCoords[i] = this._getNearestCoordToPoint(midCoord, crossPoints);
actualEndCoords[i] = crossPoints[0];
// set override
this._nodeCoordOverrideMap.set(nodes[i], actualEndCoords[i]);
}
}
}
var AS = Kekule.Glyph.ArrowSide;
var drawnElems = [];
var boundInfos = [];
var arrowElems = [];
var adjustC1, adjustC2;
var coordDelta = CU.substract(actualEndCoords[1], actualEndCoords[0]);
var w = coordDelta.x;
var h = coordDelta.y;
var l = CU.getDistance(actualEndCoords[0], actualEndCoords[1]);
var angleSin = h / l;
var angleCos = w / l;
var distance = l;
var midNo = (lineCount - 1) / 2;
var lineScreenGap = lineGap;
var deltaScreenCoord = {'y': lineScreenGap * angleCos, 'x': -lineScreenGap * angleSin};
// console.log('l', l, 'w', w, 'h', h, 'sin', angleSin, 'cos', angleCos, 'gap', lineScreenGap, 'delta', deltaScreenCoord);
var totalDeltaScreenCoord = CU.multiply(deltaScreenCoord, (lineCount - 1) / 2);
var initialOffsetCoord = CU.multiply(deltaScreenCoord, (lineCount - 1) / 2);
var c1 = CU.substract(actualEndCoords[0], initialOffsetCoord);
var c2 = CU.substract(actualEndCoords[1], initialOffsetCoord);
for (var i = 0; i < lineCount; ++i)
{
adjustC1 = null;
adjustC2 = null;
if (i !== 0)
{
c1 = CU.add(c1, deltaScreenCoord);
c2 = CU.add(c2, deltaScreenCoord);
}
// consider arrow, adjust ending coord
if (startArrowParams || endArrowParams)
{
var offsetIndex = i - midNo;
var currGap = lineScreenGap * Math.abs(offsetIndex);
//var arrowAdjustCoord = CU.multiply(deltaScreenCoord, Math.abs(i - midNo));
// start
if (startArrowParams)
{
if (startArrowParams.drawOnCenter &&
(!startArrowParams.arrowSide
|| (startArrowParams.arrowSide === AS.SINGLE && offsetIndex < 0)
|| (startArrowParams.arrowSide === AS.REVERSED && offsetIndex > 0)))
{
var adjustLength = (startArrowParams.arrowType === Kekule.Glyph.ArrowType.OPEN) ?
startArrowParams.arrowLength * currGap / (startArrowParams.arrowWidth / 2) :
startArrowParams.arrowLength;
var adjustC1 = CU.add(c1, CU.multiply(coordDelta, adjustLength / distance));
//var adjustC1 = CU.add(c1, arrowAdjustCoord);
}
if (startArrowParams.drawOnCenter && i === 0)
Kekule.ArrayUtils.pushUnique(arrowElems, this.doDrawArrowShape(context, connector, actualEndCoords[1], actualEndCoords[0], startArrowParams, shapeDrawOptions, true));
if (!startArrowParams.drawOnCenter &&
((startArrowParams.arrowSide === AS.SINGLE && i === 0) ||
(startArrowParams.arrowSide === AS.REVERSED && i === lineCount - 1)))
{
Kekule.ArrayUtils.pushUnique(arrowElems, this.doDrawArrowShape(context, connector, c2, c1, startArrowParams, shapeDrawOptions, true));
}
}
if (endArrowParams)
{
if (endArrowParams.drawOnCenter &&
(!endArrowParams.arrowSide
|| (endArrowParams.arrowSide === AS.SINGLE && offsetIndex < 0)
|| (endArrowParams.arrowSide === AS.REVERSED && offsetIndex > 0)))
{
var adjustLength = (endArrowParams.arrowType === Kekule.Glyph.ArrowType.OPEN) ?
endArrowParams.arrowLength * currGap / (endArrowParams.arrowWidth / 2) :
endArrowParams.arrowLength;
var adjustC2 = CU.substract(c2, CU.multiply(coordDelta, adjustLength / distance));
//var adjustC2 = CU.substract(c2, arrowAdjustCoord);
}
if (endArrowParams.drawOnCenter && i === 0)
Kekule.ArrayUtils.pushUnique(arrowElems, this.doDrawArrowShape(context, connector, actualEndCoords[0], actualEndCoords[1], endArrowParams, shapeDrawOptions));
if (!endArrowParams.drawOnCenter &&
((endArrowParams.arrowSide === AS.SINGLE && i === 0) ||
(endArrowParams.arrowSide === AS.REVERSED && i === lineCount - 1)))
{
Kekule.ArrayUtils.pushUnique(arrowElems, this.doDrawArrowShape(context, connector, c1, c2, endArrowParams, shapeDrawOptions));
}
}
}
var lineResult = this.doDrawLineShape(context, connector,
adjustC1 || c1, adjustC2 || c2, shapeDrawOptions, otherRenderOptions);
drawnElems.push(lineResult.element);
boundInfos.push(lineResult.boundInfo);
}
return {'drawnElems': drawnElems, 'boundInfos': boundInfos, 'arrowElems': arrowElems};
},
/** @private */
_doDrawArcConnectorShape: function(context, ctab, connector, nodes, coord1, coord2, controllerCoord, lineCount, lineGap, startArrowParams, endArrowParams, offsetBounds, shapeDrawOptions, otherRenderOptions)
{
var drawnElems = [], boundInfos = [], arrowElems = [];
var actualEndCoords = [coord1, coord2];
/*
// consider the offset bound
// TODO: currently a very rough approach, only support rect bound (for drawing arc to atom label)
var testVectors = [[coord1, controllerCoord], [controllerCoord, coord2]];
for (var i = 0; i < 2; ++i)
{
var offsetBound = offsetBounds[i];
if (offsetBound)
{
if (offsetBound.shapeType === Kekule.Render.BoundShapeType.RECT)
{
var crossPoints = Kekule.Render.MetaShapeUtils.getCrossPointsOfVectorToShapeEdges(testVectors[i], offsetBound, true);
if (crossPoints && crossPoints.length === 1) // we should draw arc to this point rather than the original end point
{
actualEndCoords[i] = crossPoints[0]; // should have only one cross point
// set override
this._nodeCoordOverrideMap.set(nodes[i], actualEndCoords[i]);
}
}
}
}
*/
// calculate out the arc center and radius, angles
// algorithm from https://blog.csdn.net/kezunhai/article/details/39476691
var midCoord1 = CU.divide(CU.add(controllerCoord, actualEndCoords[0]), 2);
var midCoord2 = CU.divide(CU.add(actualEndCoords[1], controllerCoord), 2);
var k1 = -(actualEndCoords[0].x - controllerCoord.x) / (actualEndCoords[0].y - controllerCoord.y);
var k2 = -(actualEndCoords[1].x - controllerCoord.x) / (actualEndCoords[1].y - controllerCoord.y);
var centerCoord = {
'x': (midCoord2.y - midCoord1.y- k2* midCoord2.x + k1*midCoord1.x)/(k1 - k2),
'y': midCoord1.y + k1*( midCoord2.y - midCoord1.y - k2*midCoord2.x + k2*midCoord1.x)/(k1-k2)
};
var radius = CU.getDistance(centerCoord, actualEndCoords[0]);
var vector1 = CU.substract(actualEndCoords[0], centerCoord);
var vector2 = CU.substract(actualEndCoords[1], centerCoord);
var vectorController = CU.substract(controllerCoord, centerCoord);
var startAngle = Kekule.GeometryUtils.standardizeAngle(Math.atan2(vector1.y, vector1.x));
var endAngle = Kekule.GeometryUtils.standardizeAngle(Math.atan2(vector2.y, vector2.x));
var controllerAngle = Kekule.GeometryUtils.standardizeAngle(Math.atan2(vectorController.y, vectorController.x));
// consider the offset bound
var overrideAngles = [];
for (var i = 0; i < 2; ++i)
{
var offsetBound = offsetBounds[i];
if (offsetBound)
{
//if (offsetBound.shapeType === Kekule.Render.BoundShapeType.RECT)
{
var crossAnglesAndCoords = this._getCrossCoordsAndAnglesOfArcToShapeEdges(centerCoord, radius, startAngle, endAngle, controllerAngle, offsetBound);
if (crossAnglesAndCoords && crossAnglesAndCoords.length >= 1) // we should draw arc to this angle rather than the original one
{
var actualCoord = crossAnglesAndCoords[0].coord;
var actualAngle = crossAnglesAndCoords[0].angle;
// set override
overrideAngles[i] = actualAngle;
actualEndCoords[i] = actualCoord;
this._nodeCoordOverrideMap.set(nodes[i], actualCoord);
}
}
}
}
if (overrideAngles[0])
startAngle = overrideAngles[0];
if (overrideAngles[1])
endAngle = overrideAngles[1];
var angleDelta0 = endAngle - startAngle;
var angleDelta1 = controllerAngle - startAngle;
var angleDelta2 = controllerAngle - endAngle;
var controllerSign = Math.sign(angleDelta1) * Math.sign(angleDelta2);
var arcSign = Math.sign(angleDelta0);
var anticlockwise = (controllerSign * arcSign) > 0;
// get arrow directions
var startArrowRefVector, endArrowRefVector;
var arrowRefLength = radius + lineGap * lineCount; // a sufficient length
var refAngleDelta = 10 / radius; // a moderate angle, large radius, smaller ref angle
if (startArrowParams)
{
var refAngle = startAngle - (anticlockwise? refAngleDelta: -refAngleDelta);
startArrowRefVector = {'x': Math.cos(refAngle), 'y': Math.sin(refAngle)};
}
if (endArrowParams)
{
var refAngle = endAngle + (anticlockwise? refAngleDelta: -refAngleDelta);
endArrowRefVector = {'x': Math.cos(refAngle), 'y': Math.sin(refAngle)};
//var refCoordDelta = {'x': radius * Math.cos(refAngle), 'y': radius * Math.sin(refAngle)};
//endArrowRefCoord = CU.add(centerCoord, refCoordDelta);
}
var midNo = (lineCount - 1) / 2;
var initialRadius = radius - lineGap * midNo;
// draw arc
for (var i = 0; i < lineCount; ++i)
{
var r = initialRadius + i * lineGap; // radius of current child arc
if ((startArrowParams || endArrowParams) && lineCount <= 1) // TODO: now arrow can only be drawn with one child arc
{
if (startArrowParams)
{
//if (startArrowParams.drawOnCenter && i === 0)
if (i === 0)
{
var arrowRefCoord = CU.add(centerCoord, CU.multiply(startArrowRefVector, r));
Kekule.ArrayUtils.pushUnique(arrowElems, this.doDrawArrowShape(context, connector, arrowRefCoord, actualEndCoords[0], startArrowParams, shapeDrawOptions, true));
}
}
if (endArrowParams)
{
//if (endArrowParams.drawOnCenter && i === 0)
if (i === 0)
{
var arrowRefCoord = CU.add(centerCoord, CU.multiply(endArrowRefVector, r));
Kekule.ArrayUtils.pushUnique(arrowElems, this.doDrawArrowShape(context, connector, arrowRefCoord, actualEndCoords[1], endArrowParams, shapeDrawOptions, true));
}
}
}
var pathResult = this.doDrawArcShape(context, centerCoord, r, startAngle, endAngle, anticlockwise, shapeDrawOptions);
if (pathResult.element)
drawnElems.push(pathResult.element);
if (pathResult.boundInfo)
boundInfos.push(pathResult.boundInfo);
}
var result = {'drawnElems': drawnElems, 'boundInfos': boundInfos, 'arrowElems': arrowElems};
return result;
},
/** @private */
_getCrossCoordsAndAnglesOfArcToShapeEdges: function(arcCenter, arcRadius, arcStartAngle, arcEndAngle, arcControllerAngle, shapeInfo)
{
var edgeElements = Kekule.Render.MetaShapeUtils.getEdgeBasicElements(shapeInfo);
var edgeVectors = edgeElements.vectors;
var candidates = [];
for (var i = 0, l = edgeVectors.length; i < l; ++i)
{
var edge = edgeVectors[i];
var crossPoints = Kekule.GeometryUtils.getCrossPointsOfVectorToCircle(edge, arcCenter, arcRadius, 1e-4); // TODO: currently threshold fixed, a rather large value for screen coord sys
if (crossPoints && crossPoints.length)
{
candidates = candidates.concat(crossPoints);
}
}
var edgeCircles = edgeElements.circles;
for (var i = 0, l = edgeCircles.length; i < l; ++i)
{
var edge = edgeCircles[i];
var crossPoints = Kekule.GeometryUtils.getCrossPointsOfCircles(edge.center, edge.radius, arcCenter, arcRadius, 1e-4); // TODO: currently threshold fixed, a rather large value for screen coord sys
if (crossPoints && crossPoints.length)
{
candidates = candidates.concat(crossPoints);
}
}
//console.log(edgeElements, candidates);
var result = [];
var sign = Math.sign((arcControllerAngle - arcStartAngle) * (arcControllerAngle - arcEndAngle));
for (var i = 0, l = candidates.length; i < l; ++i)
{
var delta = CU.substract(candidates[i], arcCenter);
var angle = Kekule.GeometryUtils.standardizeAngle(Math.atan2(delta.y, delta.x));
if (Math.sign((angle - arcStartAngle) * (angle - arcEndAngle)) === sign)
result.push({'angle': angle, 'coord': candidates[i]});
}
return result;
},
/** @private */
_getNearestCoordToPoint: function(pointCoord, coords)
{
var result = null;
var minDistance = null;
for (var i = 0, l = coords.length; i < l; ++i)
{
var distance = CU.getDistance(coords[i], pointCoord);
if (!result || distance < minDistance)
{
result = coords[i];
minDistance = distance;
}
}
return result;
},
/** @private */
_transformArrowLength: function(context, arrowLength, arrowWidth)
{
var coord = {'x': arrowLength || 0, 'y': arrowWidth || 0};
var transformed = Kekule.CoordUtils.substract(
this.transformCoordToContext(context, this.getChemObj(), coord),
this.transformCoordToContext(context, this.getChemObj(), {'x': 0, 'y': 0})
);
return {
'arrowLength': Math.abs(transformed.x),
'arrowWidth': Math.abs(transformed.y)
};
},
/** @private */
doDrawArrowShape: function(context, connector, coord1, coord2, arrowParams, drawOptions, reversed)
{
// draw an arrow at end of line (coord2)
var AS = Kekule.Glyph.ArrowSide;
var AT = Kekule.Glyph.ArrowType;
var coordDelta = CU.substract(coord2, coord1);
var distance = CU.getDistance(coord2, coord1);
var sinAngle = coordDelta.y / distance;
var cosAngle = coordDelta.x / distance;
var arrowLength = arrowParams.arrowLength; // || 3;
var arrowWidth = arrowParams.arrowWidth; // || 6;
var halfArrowWidth = arrowWidth / 2;
var basePointCoord = CU.substract(coord2, CU.multiply(coordDelta, arrowLength / distance));
var offsetCoord = {
'x': halfArrowWidth * sinAngle,
'y': -halfArrowWidth * cosAngle
};
var arrowWingEnd1;
var arrowWingEnd2;
var PU = Kekule.Render.DrawPathUtils;
var pathArray = [];
//var elems = [];
// wingEnd1
if (!arrowParams.arrowSide
|| (!reversed && arrowParams.arrowSide === AS.SINGLE)
|| (reversed && arrowParams.arrowSide === AS.REVERSED))
{
arrowWingEnd1 = CU.add(basePointCoord, offsetCoord);
/*
var elem = this.drawLine(context, coord2, arrowWingEnd1, drawOptions);
if (elem)
elems.push(elem);
*/
pathArray.push('M');
pathArray.push([arrowWingEnd1.x, arrowWingEnd1.y]);
pathArray.push('L');
pathArray.push([coord2.x, coord2.y]);
}
else
{
pathArray.push('M');
pathArray.push([coord2.x, coord2.y]);
}
// wingEnd2
if (!arrowParams.arrowSide
|| (!reversed && arrowParams.arrowSide === AS.REVERSED)
|| (reversed && arrowParams.arrowSide === AS.SINGLE))
{
arrowWingEnd2 = CU.substract(basePointCoord, offsetCoord);
/*
elem = this.drawLine(context, coord2, arrowWingEnd2, drawOptions);
if (elem)
elems.push(elem);
*/
pathArray.push('L');
pathArray.push([arrowWingEnd2.x, arrowWingEnd2.y]);
}
var doFill = false;
// close line
if (arrowParams.arrowType === AT.TRIANGLE)
{
/*
var elem = this.drawLine(context, arrowWingEnd1 || basePointCoord, arrowWingEnd2 || basePointCoord, drawOptions);
if (elem)
elems.push(elem);
*/
var endCoord = (arrowWingEnd1 && arrowWingEnd2)? null: basePointCoord;
if (endCoord)
{
pathArray.push('L');
pathArray.push([endCoord.x, endCoord.y]);
}
pathArray.push('Z');
doFill = true;
}
//return elems;
var path = PU.makePath.apply(this, pathArray);
var drawOps = Object.create(drawOptions);
if (!doFill)
{
drawOps.fillColor = 'transparent';
}
return this.drawPath(context, path, drawOps);
},
/** @private */
doDrawLineShape: function(context, connector, coord1, coord2, drawOptions, renderOptions)
{
var elem = this.drawLine(context, coord1, coord2, drawOptions);
var boundInfo = this.createLineBoundInfo(coord1, coord2, drawOptions.strokeWidth);
return {'element': elem, 'boundInfo': boundInfo};
},
/** @private */
doDrawArcShape: function(context, centerCoord, radius, startAngle, endAngle, anticlockwise, drawOptions)
{
var overrideDrawOps = Object.create(drawOptions);
overrideDrawOps.fillColor = null; // do not fill, only stroke
var elem = this.drawArc(context, centerCoord, radius, startAngle, endAngle, anticlockwise, overrideDrawOps);
var boundInfo = this.createArcBoundInfo(centerCoord, radius, startAngle, endAngle, anticlockwise, overrideDrawOps.strokeWidth);
return {'element': elem, 'boundInfo': boundInfo};
}
});
/**
* Class to render a path glyph object.
* @class
* @augments Kekule.Render.BaseGlyph2DRenderer
*/
Kekule.Render.PathGlyph2DRenderer = Class.create(Kekule.Render.BaseGlyph2DRenderer,
/** @lends Kekule.Render.PathGlyph2DRenderer# */
{
/** @private */
CLASS_NAME: 'Kekule.Render.PathGlyph2DRenderer',
/** @constructs */
initialize: function(/*$super, */chemObj, drawBridge, parent)
{
this.tryApplySuper('initialize', [chemObj, drawBridge, parent]) /* $super(chemObj, drawBridge, parent) */;
this._concreteChemObj = chemObj.getCtab();
this._concreteRenderer = new Kekule.Render.PathGlyphCtab2DRenderer(chemObj.getCtab(), drawBridge, this);
},
finalize: function(/*$super*/)
{
this.tryApplySuper('finalize') /* $super() */;
if (this._concreteRenderer)
{
this._concreteRenderer.finalize();
this._concreteRenderer = null;
}
},
/** ignore */
_getRenderSortIndex: function(/*$super*/)
{
if (this._concreteRenderer)
return this._concreteRenderer._getRenderSortIndex();
else
return this.tryApplySuper('_getRenderSortIndex') /* $super() */;
},
/** @ignore */
getRenderCache: function(context)
{
return this._concreteRenderer.getRenderCache(context);
},
/** @private */
_getConcreteRendererDrawOptions: function(options)
{
return options;
},
/** @ignore */
isChemObjRenderedBySelf: function(/*$super, */context, obj)
{
var result = this.tryApplySuper('isChemObjRenderedBySelf', [context, obj]) /* $super(context, obj) */ || (obj === this.getChemObj()) || this._concreteRenderer.isChemObjRenderedBySelf(context, obj);
return result;
},
/** @ignore */
isChemObjRenderedDirectlyBySelf: function(/*$super, */context, obj)
{
return this.tryApplySuper('isChemObjRenderedDirectlyBySelf', [context, obj]) /* $super(context, obj) */ || (obj === this.getChemObj());
},
/** @private */
doSetRedirectContext: function(/*$super, */value)
{
this.tryApplySuper('doSetRedirectContext', [value]) /* $super(value) */;
this._concreteRenderer.setRedirectContext(value);
},
/** @ignore */
doDraw: function(/*$super, */context, baseCoord, options)
{
//console.log('dodraw path', this._concreteRenderer.getClassName());
this.tryApplySuper('doDraw', [context, baseCoord, options]) /* $super(context, baseCoord, options) */;
var chemObj = this.getChemObj();
var op = Object.create(options);
if (op.partialDrawObjs) // path glyph is a whole and can not be partial drawn
op.partialDrawObjs = null;
return this._concreteRenderer.draw(context, baseCoord, this._getConcreteRendererDrawOptions(op));
},
/** @ignore */
doRedraw: function(context)
{
return this._concreteRenderer.redraw(context);
},
/** @ignore */
doUpdate: function(context, updatedObjDetails, updateType)
{
var objs = this._extractObjsOfUpdateObjDetails(updatedObjDetails);
if (objs.indexOf(this.getChemObj()) >= 0) // root object need to be updated
{
var p = this.getRenderCache(context);
this.doClear(context);
return this.draw(context, p.baseCoord, p.options);
}
return this._concreteRenderer.doUpdate(context, updatedObjDetails, updateType);
},
/** @ignore */
doClear: function(context)
{
return this._concreteRenderer.clear(context);
},
/** @ignore */
estimateRenderBox: function(context, options, allowCoordBorrow)
{
return this._concreteRenderer.estimateRenderBox(context, this._getConcreteRendererDrawOptions(options), allowCoordBorrow);
},
/** @ignore */
transformCoordToObj: function(context, chemObj, coord)
{
//console.log(chemObj, this.getChemObj(), chemObj === this.getChemObj());
var obj = (this.getChemObj() === chemObj)? this._concreteChemObj: chemObj;
return this._concreteRenderer.transformCoordToObj(context, obj, coord);
},
/** @ignore */
transformCoordToContext: function(context, chemObj, coord)
{
//console.log(chemObj, this.getChemObj(), chemObj === this.getChemObj());
var obj = (this.getChemObj() === chemObj)? this._concreteChemObj: chemObj;
return this._concreteRenderer.transformCoordToContext(context, obj, coord);
}
});
Kekule.Render.Renderer2DFactory.register(Kekule.Glyph.PathGlyph, Kekule.Render.PathGlyph2DRenderer);
})();