这是绘画系统的第撒个大部分,连线,连线的内容包括最上层的链接线抽象类,和细分的直线,折线,贝塞尔曲线以及内外旋轮曲线五个部分。
首先是连接线抽象类。
连接线抽象类,需要成为连接线的类都可以混入此抽象类的实现。为了方便实现一些复杂的连接场景,特别注意:
1.连接线总是画在全局坐标系中。
2.连接线可以移动位置,但不能缩放、旋转、斜切。
3.连接线只有两个端点,即使是折线,也是两个端点,不会有更多。
4.连线不属于任何分组。
5.混入此实现的类默认假定已经混入了 Eventful ,因为我们需要事件系统。
function CableLike() {
this.isCable = false;
this.showLinkHooks = false;
this.startBounding = null; // 起始形状的边界矩形
this.endBounding = null; // 结束形状的边界矩形
this.arrowType = 'both'; // 结束,开始,两者都有
this.arrowAngel = Math.PI / 8;
this.arrowLength = 10;
this.fromId = ''; // 开始元素的ID。
this.toId = ''; // 结束元素的ID。
this.fromPosition = ''; // 起始元素的连接位置
this.toPosition = ''; // 结束元素的连接位置
this.startHook = new LinkHook({
el: this,
name: 'START'
});
this.endHook = new LinkHook({
el: this,
name: 'END'
});
this.on('afterRender', this.afterRenderHandler, this);
}
这里是对连线的基本属性的定义过程,其中加了注释,包括了起始和结束的边界形状,箭头式的情况下是起始还是结束还是两者都有箭头,起始和结束的元素以及两者的位置。
后面大段的定义了不同方式的调用以及渲染方式,这里就不多作展示了。
__renderArrow: function __renderArrow(twoPoints) {
var _this$ctx;
var p1 = twoPoints[0];
var p2 = twoPoints[1];
p2[0] = p2[0] - p1[0];
p2[1] = p2[1] - p1[1];
var cosp2 = matrixUtil.cosx.apply(matrixUtil, _toConsumableArray(p2));
var sinp2 = matrixUtil.sinx.apply(matrixUtil, _toConsumableArray(p2));
var cosArrow = mathCos(this.arrowAngel);
var sinArrow = mathSin(this.arrowAngel);
var x1 = this.arrowLength * (cosp2 * cosArrow - sinp2 * sinArrow);
var y1 = this.arrowLength * (sinp2 * cosArrow + cosp2 * sinArrow);
var x2 = this.arrowLength * (cosp2 * cosArrow + sinp2 * sinArrow);
var y2 = this.arrowLength * (sinp2 * cosArrow - cosp2 * sinArrow);
x1 += p1[0];
y1 += p1[1];
x2 += p1[0];
y2 += p1[1];
this.ctx.save();
this.ctx.strokeStyle = this.style.stroke;
this.ctx.fillStyle = this.style.stroke;
this.ctx.beginPath();
(_this$ctx = this.ctx).moveTo.apply(_this$ctx, _toConsumableArray(p1));
this.ctx.lineTo(x1, y1);
this.ctx.lineTo(x2, y2);
this.ctx.closePath();
this.ctx.stroke();
this.ctx.fill();
this.ctx.restore();
},
这是连线的过程,大体上可以分为四步:
1.将原点移动到终点
2.求出变化的角度
3.将原点移回(0,0)
4.利用刚才的数据画出路径,期间会利用返回参数的不同调用不同的函数。
接着就是最有代表性的直线绘制
直线。所有线条的特征是:
1.线条可以移动位置,但不能进行其它仿射变换,scale/rotate/skew 都不可以。所以计算线条的相关参数时可以做简化,只要计算 position 和 translate 就可以了。
2.线条总是画在全局空间中。
3.线条不能加到 Group 中。
首先是构造线条的函数
function Line(options) {
var _this;
_classCallCheck(this, Line);
_this = _super.call(this, dataUtil.merge({
shape: {
x1: 0,
y1: 0,
x2: 0,
y2: 0,
percent: 1
},
style: {
stroke: '#000',
fill: null
}
}, options, true));
_this.type = 'line';
classUtil.inheritProperties(_assertThisInitialized(_this), CableLike, _this.options);
classUtil.copyOwnProperties(_assertThisInitialized(_this), _this.options, ['style', 'shape']);
_this.transformable = false;
return _this;
}
在获得来自前面抽象类的信息后会开始进行这一内容调用,通过规定线的开始结束百分比等数据来构造线段,百分比用于后期获取线上特定点。
_createClass(Line, [{
key: "buildPath",
value: function buildPath(ctx, shape) {
var x1;
var y1;
var x2;
var y2;
if (this.subPixelOptimize) {
var subPixelOptimizeOutputShape = {};
subPixelOptimizeLine(subPixelOptimizeOutputShape, shape, this.style);
x1 = subPixelOptimizeOutputShape.x1;
y1 = subPixelOptimizeOutputShape.y1;
x2 = subPixelOptimizeOutputShape.x2;
y2 = subPixelOptimizeOutputShape.y2;
} else {
x1 = shape.x1;
y1 = shape.y1;
x2 = shape.x2;
y2 = shape.y2;
}
var percent = shape.percent;
if (percent === 0) {
return;
}
ctx.moveTo(x1, y1);
if (percent < 1) {
x2 = x1 * (1 - percent) + x2 * percent;
y2 = y1 * (1 - percent) + y2 * percent;
}
ctx.lineTo(x2, y2);
}
这里就是构建当前线条的路径,数据结构类似前面讲过的SVG中的 path 属性,方法也大同小异
key: "renderTransformControls",
value: function renderTransformControls(ctx, prevEl) {}
这里是关键,也是比较特殊的地方,其作用为禁用 Elemnet 类上的 renderTransformControls 方法,因为我们不希望在线条上使用几何变换。
后面获取百分比位置的点的功能与前面几何中的线内容极为相似,就不多说了。
后面主要就是几种不同的线,构建的基本方法是一样的,思路也完全相同,只是因为线的特征而算法不同。
其中内外旋轮类曲线可分为:外切外摆线、渐开线、内切外摆线、内摆线和直滚摆线。该分类的规律是:
(1)从外切外摆线到渐开线是滚圆曲率半径由小变无限大;
(2)从渐开线到内切外摆线是滚圆曲率改变方向
(3)从内切外摆线到内摆线是滚圆曲率半径由大变到小于基圆半径;
(4)从内摆线到直滚摆线是基圆曲率半径由小变无限大。
从这个变化规律可以看出摆线和渐开线是性质上相同的曲线,可以把它们归入一族,将它们取名为摆线族曲线。
而贝塞尔曲线由线段与节点组成,节点是可拖动的支点,线段像可伸缩的皮筋,我们在绘图工具上看到的钢笔工具就是来做这种矢量曲线的。详细内容感兴趣的可以自己去查一下,我在这里三言两语也说不清楚。
总之连线部分的内容就这么多了。