回顾

上一篇说到:ZRender源码分析4:Painter(View层)-中,这次,来补充一下具体的shape

关于热区的边框

以圆形为例:

document.addEventListener('DOMContentLoaded', function () {
var canvasDom = document.getElementById('canvasId'),
context = canvasDom.getContext('2d'); context.lineWidth = 50;
context.arc(100, 100, 50, 0, Math.PI * 2);
context.stroke(); context.lineWidth = 1;
context.moveTo(0,100);
context.lineTo(200,100); context.stroke();
});

得到的图形如下:


arc方法中,参数分别为x,y,r,startAngle,endAngle,但是经过测量,这个圆形的总宽度不是2r(100),而是150。迷惑了很久,才明白r是圆心到边框中央的长度,而lineWidth比较小的时候,是看不出这种差别的。 如果要获得热区的宽度,那就是2 * r+ lineWidth/2 + lineWidth / 2,也就是 2 * r + lineWidth。而热区的最左端就是 x-r-lineWidth / 2,最上端就是 y-r-lineWidth / 2。这就解释了在zrender.shape.Circle类中的getRect方法。

getRect : function (style) {
if (style.__rect) {
return style.__rect;
} var lineWidth;
if (style.brushType == 'stroke' || style.brushType == 'fill') {
lineWidth = style.lineWidth || 1;
}
else {
lineWidth = 0;
}
style.__rect = {
x : Math.round(style.x - style.r - lineWidth / 2),
y : Math.round(style.y - style.r - lineWidth / 2),
width : style.r * 2 + lineWidth,
height : style.r * 2 + lineWidth
}; return style.__rect;
}

先判断传入的style中是否有__rect这个属性,如果有直接返回,缓存,免得进行多次计算。如果brushType为stroke或者fill,确保有lineWidth,默认为1。最后根据上述算法计算出热点区域。其他图形关于lineWidth的计算都跟这个很相似,以后就不再赘述了。

关于矩形

主要看下圆角矩形的画法:

_buildRadiusPath: function(ctx, style) {
//左上、右上、右下、左下角的半径依次为r1、r2、r3、r4
//r缩写为1 相当于 [1, 1, 1, 1]
//r缩写为[1] 相当于 [1, 1, 1, 1]
//r缩写为[1, 2] 相当于 [1, 2, 1, 2]
//r缩写为[1, 2, 3] 相当于 [1, 2, 3, 2]
var x = style.x;
var y = style.y;
var width = style.width;
var height = style.height;
var r = style.radius;
var r1;
var r2;
var r3;
var r4; if(typeof r === 'number') {
r1 = r2 = r3 = r4 = r;
}
else if(r instanceof Array) {
if (r.length === 1) {
r1 = r2 = r3 = r4 = r[0];
}
else if(r.length === 2) {
r1 = r3 = r[0];
r2 = r4 = r[1];
}
else if(r.length === 3) {
r1 = r[0];
r2 = r4 = r[1];
r3 = r[2];
} else {
r1 = r[0];
r2 = r[1];
r3 = r[2];
r4 = r[3];
}
} else {
r1 = r2 = r3 = r4 = 0;
}
ctx.moveTo(x + r1, y);
ctx.lineTo(x + width - r2, y);
r2 !== 0 && ctx.quadraticCurveTo(
x + width, y, x + width, y + r2
);
ctx.lineTo(x + width, y + height - r3);
r3 !== 0 && ctx.quadraticCurveTo(
x + width, y + height, x + width - r3, y + height
);
ctx.lineTo(x + r4, y + height);
r4 !== 0 && ctx.quadraticCurveTo(
x, y + height, x, y + height - r4
);
ctx.lineTo(x, y + r1);
r1 !== 0 && ctx.quadraticCurveTo(x, y, x + r1, y);
},
  • zrender中圆角矩形是用二次贝塞尔曲线画的,关于二次贝塞尔请看 HTML5 canvas quadraticCurveTo() 方法
  • 还有一种是可以用arcTo方法,请看 html5 Canvas画图10:圆角矩形
  • 确定各个边角上的圆角半径,顺序为左上,右上,右下,坐下,这样兼容比较灵活。
  • 这里只举例说明前三句,其他都是同理。a.将当前点移动到左上角的右边(加上r1)。b.画出顶部的线 c.用二次贝塞尔曲线画出圆角,如下图所示
  • 在API中,没有公布圆角矩形的功能(为什么呢)。但是我们可以这样用:
    // 矩形
    var RectangleShape = require('zrender/shape/Rectangle');
    zr.addShape(new RectangleShape({
    style : {
    x : 100,
    y : 100,
    width : 100,
    height : 50,
    color : 'rgba(135, 206, 250, 0.8)',
    text:'rectangle',
    textPosition:'inside',
    radius: [1,2,3,4]
    },
    draggable : true
    }));
    zr.render();

关于椭圆

椭圆的画法有多种,请看这里 在HTML5的Canvas上绘制椭圆的几种方法,zrender用的是三次贝塞尔曲线法二

关于虚线

如果是实线(solid),直接moveTo,lineTo就搞定了,那虚线怎么画呢?看这里: HTML5 Canvas自定义圆角矩形与虚线(Rounded Rectangle and Dash Line)。zrender中将线的类型分为3种,solid(默认),dashed(虚线),dotted(点线)。 其实虚线和点线性质是一样的,只是线长不一样罢了。

// zrender.shape.Line
buildPath : function(ctx, style) {
if (!style.lineType || style.lineType == 'solid') {
//默认为实线
ctx.moveTo(style.xStart, style.yStart);
ctx.lineTo(style.xEnd, style.yEnd);
}
else if (style.lineType == 'dashed'
|| style.lineType == 'dotted'
) {
var dashLength =(style.lineWidth || 1)
* (style.lineType == 'dashed' ? 5 : 1);
dashedLineTo(
ctx,
style.xStart, style.yStart,
style.xEnd, style.yEnd,
dashLength
);
}
} // zrender.util.dashedLineTo
/**
* 虚线lineTo
*/
return function (ctx, x1, y1, x2, y2, dashLength) {
dashLength = typeof dashLength != 'number'
? 5
: dashLength; var deltaX = x2 - x1;
var deltaY = y2 - y1;
var numDashes = Math.floor(
Math.sqrt(deltaX * deltaX + deltaY * deltaY) / dashLength
); for (var i = 0; i < numDashes; ++i) {
ctx[i % 2 ? 'lineTo' : 'moveTo'](
x1 + (deltaX / numDashes) * i,
y1 + (deltaY / numDashes) * i
);
}
ctx.lineTo(x2, y2);
};

可以看到,dashed和dotted的区别就只有一个dashLength(5或者1,不太灵活吧,不能自定义哦),实现思路也很明确:先计算出线的长度(勾股定理),然后计算一共分为多少段,最后用moveTo和lineTo一直画,就行了。

关于图片

brush : function(ctx, isHighlight, refresh) {
var style = this.style || {}; if (isHighlight) {
// 根据style扩展默认高亮样式
style = this.getHighlightStyle(
style, this.highlightStyle || {}
);
} var image = style.image;
var me = this; if (typeof(image) === 'string') {
var src = image;
if (_cache[src]) {
image = _cache[src];
}
else {
image = new Image();//document.createElement('image');
image.onload = function(){
image.onload = null;
clearTimeout( _refreshTimeout );
_needsRefresh.push( me );
// 防止因为缓存短时间内触发多次onload事件
_refreshTimeout = setTimeout(function(){
refresh && refresh( _needsRefresh );
// 清空needsRefresh
_needsRefresh = [];
}, 10);
};
_cache[ src ] = image; image.src = src;
}
}
if (image) {
//图片已经加载完成
if (window.ActiveXObject) {
if (image.readyState != 'complete') {
return;
}
}
else {
if (!image.complete) {
return;
}
} ctx.save();
this.setContext(ctx, style); // 设置transform
this.updateTransform(ctx); var width = style.width || image.width;
var height = style.height || image.height;
var x = style.x;
var y = style.y;
if (style.sWidth && style.sHeight) {
var sx = style.sx || 0;
var sy = style.sy || 0;
ctx.drawImage(
image,
sx, sy, style.sWidth, style.sHeight,
x, y, width, height
);
}
else if (style.sx && style.sy) {
var sx = style.sx;
var sy = style.sy;
var sWidth = width - sx;
var sHeight = height - sy;
ctx.drawImage(
image,
sx, sy, sWidth, sHeight,
x, y, width, height
);
}
else {
ctx.drawImage(image, x, y, width, height);
}
// 如果没设置宽和高的话自动根据图片宽高设置
style.width = width;
style.height = height;
this.style.width = width;
this.style.height = height; if (style.text) {
this.drawText(ctx, style, this.style);
} ctx.restore();
}
}, /**
* 创建路径,用于判断hover时调用isPointInPath~
* @param {Context2D} ctx Canvas 2D上下文
* @param {Object} style 样式
*/
buildPath : function(ctx, style) {
ctx.rect(style.x, style.y, style.width, style.height);
return;
},
  • ImageShape覆盖了父类的buildPath和brush方法,其中buildPath用于判断hover时调用isPointInPath,由于Image特殊,所以覆盖了brush方法
  • ImageShape的style.image 可以配置一个string或者ImageElement对象,这就要分情况处理了,其中,_cache为缓存,提高效率
  • 如果Image是个字符串,则new Image()注册onload事件,在onload回调中执行refresh方法(其实是执行了painter.update方法),update又会执行brush动作,再次进去该方法
  • 判断图片是否已经加载完成,如果没完成,说明image传入的是字符串,并且为第一次进入方法,如果image是字符串第二次进入或者image传入的是DOM对象,继续向下执行
  • 其他代码同Base.js,不同的是调用了drawImage的多重重载,如果没有设置图片的宽高,直接取真实的宽高。

关于文字

先看getRect:

/**
* 返回矩形区域,用于局部刷新和文字定位
* @param {Object} style
*/
getRect : function(style) {
if (style.__rect) {
return style.__rect;
} var width = area.getTextWidth(style.text, style.textFont);
var height = area.getTextHeight(style.text, style.textFont); var textX = style.x; //默认start == left
if (style.textAlign == 'end' || style.textAlign == 'right') {
textX -= width;
}
else if (style.textAlign == 'center') {
textX -= (width / 2);
} var textY;
if (style.textBaseline == 'top') {
textY = style.y;
}
else if (style.textBaseline == 'bottom') {
textY = style.y - height;
}
else {
// middle
textY = style.y - height / 2;
} style.__rect = {
x : textX,
y : textY,
width : width,
height : height
}; return style.__rect;
}

为了更好地理解,进行如下测试

zr.addShape(new LineShape(
{
style:
{
xStart: 0,
yStart: 100,
xEnd: 300,
yEnd: 100,
strokeColor: 'black',
lineWidth: 1
}
})); zr.addShape(new LineShape(
{
style:
{
xStart: 100,
yStart: 0,
xEnd: 100,
yEnd: 300,
strokeColor: 'black',
lineWidth: 1
}
})); zr.addShape(new TextShape(
{
style:
{
x: 100,
y: 100,
color: 'red',
text: 'Align:right;\nBaseline:bottom',
textAlign: 'right',
textBaseline: 'bottom'
},
hoverable: true,
zlevel: 2
})); zr.addShape(new TextShape(
{
style:
{
x: 100,
y: 100,
color: 'red',
text: 'Align:right;\nBaseline:top',
textAlign: 'right',
textBaseline: 'top'
},
hoverable: true,
zlevel: 2
})); zr.addShape(new TextShape(
{
style:
{
x: 100,
y: 100,
color: 'red',
text: 'Align:left;\nBaseline:bottom',
textAlign: 'left',
textBaseline: 'bottom'
},
hoverable: true,
zlevel: 2
})); zr.addShape(new TextShape(
{
style:
{
x: 100,
y: 100,
color: 'red',
text: 'Align:left;\nBaseline:top',
textAlign: 'left',
textBaseline: 'top'
},
hoverable: true,
zlevel: 2
})); zr.render();

效果如下:
可见,x,y只是一个基准点,并不是左上角的点。所以在getRect中需要重新计算热区。

  • 通过area.getTextWidth和area.getTextHeight得到文字所占的宽高,这两个方法在以前有讲解。
  • 通过textAlign和textBaseline计算出文字左上角的x和y
  • 返回x/y/width/height

TextShape依旧覆盖了Base类的brush方法,如下:

brush : function(ctx, isHighlight) {
var style = this.style;
if (isHighlight) {
// 根据style扩展默认高亮样式
style = this.getHighlightStyle(
style, this.highlightStyle || {}
);
} if (typeof style.text == 'undefined') {
return;
} ctx.save();
this.setContext(ctx, style); // 设置transform
this.updateTransform(ctx); if (style.textFont) {
ctx.font = style.textFont;
}
ctx.textAlign = style.textAlign || 'start';
ctx.textBaseline = style.textBaseline || 'middle'; var text = (style.text + '').split('\n');
var lineHeight = area.getTextHeight('国', style.textFont);
var rect = this.getRect(style);
var x = style.x;
var y;
if (style.textBaseline == 'top') {
y = rect.y;
}
else if (style.textBaseline == 'bottom') {
y = rect.y + lineHeight;
}
else {
y = rect.y + lineHeight / 2;
} for (var i = 0, l = text.length; i < l; i++) {
if (style.maxWidth) {
switch (style.brushType) {
case 'fill':
ctx.fillText(
text[i],
x, y, style.maxWidth
);
break;
case 'stroke':
ctx.strokeText(
text[i],
x, y, style.maxWidth
);
break;
case 'both':
ctx.fillText(
text[i],
x, y, style.maxWidth
);
ctx.strokeText(
text[i],
x, y, style.maxWidth
);
break;
default:
ctx.fillText(
text[i],
x, y, style.maxWidth
);
}
}
else{
switch (style.brushType) {
case 'fill':
ctx.fillText(text[i], x, y);
break;
case 'stroke':
ctx.strokeText(text[i], x, y);
break;
case 'both':
ctx.fillText(text[i], x, y);
ctx.strokeText(text[i], x, y);
break;
default:
ctx.fillText(text[i], x, y);
}
}
y += lineHeight;
} ctx.restore();
return;
},
  • brush方法与Base.brush方法大致相同,这里只说不同的。如果textAlign和textBaseline没有赋值,给予默认值
  • 关于fillText和strokeText,请看 HTML5 canvas fillText() 方法HTML5 canvas strokeText() 方法,注意:这两个方法是可以传入maxWidth的
  • fillText或者strokeText时的x取得是style.x,因为text可能有多行,所以传入fillText中的y需要进行重新计算(根据textBaseline和rect.y和行高)
  • 将text根据\n(换行)分隔成数组,遍历进行绘图,每个遍历最后是将y加上lineHeight,以实现多行。

关于圆环

buildPath : function(ctx, style) {
// 非零环绕填充优化
ctx.arc(style.x, style.y, style.r, 0, Math.PI * 2, false);
ctx.moveTo(style.x + style.r0, style.y);
ctx.arc(style.x, style.y, style.r0, 0, Math.PI * 2, true);
return;
},

关于贝塞尔曲线、心形、水滴

  • 分为二次贝塞尔曲线和三次贝塞尔曲线 请看:HTML5 canvas quadraticCurveTo() 方法HTML5 canvas bezierCurveTo() 方法
  • zrender只是将二次和三次统一到一个图形里面做了封装,getRect也很简单,只是取这些个点的最大值与最小值进行计算,其他没什么特别之处,不贴代码了。
  • 心形(Heart)和水滴(Droplet)都是贝塞尔曲线绘制而成,不分析了就。

关于玫瑰线

请参考如下3个链接,太不常用了,不细细分析了。

  • http://xuxzmail.blog.163.com/blog/static/251319162009739563225/
  • http://en.wikipedia.org/wiki/Rose_(mathematics)
  • https://github.com/shimobayashi/rose-curve-canvas/blob/master/index.html

总结

剩余折线,多边形,正多边形,路径,扇形,五角星,内外旋轮曲线,下次再说。

ZRender源码分析5:Shape绘图详解的更多相关文章

  1. 死磕 java并发包之AtomicStampedReference源码分析(ABA问题详解)

    问题 (1)什么是ABA? (2)ABA的危害? (3)ABA的解决方法? (4)AtomicStampedReference是什么? (5)AtomicStampedReference是怎么解决AB ...

  2. Laravel源码分析--Laravel生命周期详解

    一.XDEBUG调试 这里我们需要用到php的 xdebug 拓展,所以需要小伙伴们自己去装一下,因为我这里用的是docker,所以就简单介绍下在docker中使用xdebug的注意点. 1.在php ...

  3. spring源码分析之spring-web http详解

    spring-web是spring webmvc的基础,它的功能如下: 1. 封装http协议中client端/server端的request请求和response响应及格式的转换,如json,rss ...

  4. spring源码分析之spring-jms模块详解

    0 概述 spring提供了一个jms集成框架,这个框架如spring 集成jdbc api一样,简化了jms api的使用. jms可以简单的分成两个功能区,消息的生产和消息的消费.JmsTempl ...

  5. spring源码分析之spring-messaging模块详解

    0 概述 spring-messaging模块为集成messaging api和消息协议提供支持. 其代码结构为: 其中base定义了消息Message(MessageHeader和body).消息处 ...

  6. spring源码分析之spring-jdbc模块详解

    0 概述 Spring将替我们完成所有使用JDBC API进行开发的单调乏味的.底层细节处理工作.下表描述了哪些是spring帮助我们做好的,哪些是我们要做的. Action  Spring  You ...

  7. zrender源码分析3--初始化Painter绘图模块

    接上次分析到初始化ZRender的源码,这次关注绘图模块Painter的初始化 入口1:new Painter(dom, this.storage); // zrender.js /** * ZRen ...

  8. ZRender源码分析2:Storage(Model层)

    回顾 上一篇请移步:zrender源码分析1:总体结构 本篇进行ZRender的MVC结构中的M进行分析 总体理解 上篇说到,Storage负责MVC层中的Model,也就是模型,对于zrender来 ...

  9. ZRender源码分析4:Painter(View层)-中

    回顾 上一篇说到:ZRender源码分析3:Painter(View层)-上,接上篇,开始Shape对象 总体理解 先回到上次的Painter的render方法 /** * 首次绘图,创建各种dom和 ...

随机推荐

  1. list集合接口

    import java.util.ArrayList; import java.util.List; class Phone { private String brand; private doubl ...

  2. hbase性能调优之压缩测试

    文章概述: 1.顺序写 2.顺序读 3.随机写 4.随机读 5.SCAN数据 0 性能测试工具 hbase org.apache.hadoop.hbase.PerformanceEvaluation ...

  3. Apache+php+mysql win7 64位安装的几个注意事项

    网上一堆安装教程,所以不赘述具体安装过程,只说注意事项.新手推荐phpstudy 如果想单个安装,那么以下是我两三年内多次在win winserver环境下配置Apache环境的一点注意事项,下载连接 ...

  4. 使用Hashtable和List结合拼json数据

    在做项目的时候,有时候需要向页面返回一个特定的json类型的数据,一般情况下会有下面的方法进行拼接: public String chongzhiList() throws Exception { L ...

  5. C#实现网页表单自动提交

    首先,设计一个简单的Form界面,好直观的查看登录情况,界面如图下图所示: 然后在 webBrowser1_DocumentCompleted函数中添加如下代码: private void webBr ...

  6. IDEA12 KeyGen Download List

    When you use IDEA to develop Java, you can use the following file to generate lincese. Because CNBlo ...

  7. 【Robot Framework 介绍】总纲

    Robot Framework是一个由python构建的的开源的自动化测试框架,现在版本还在不停的更新中.由于它开源性,网上有大量的第三方接口和很多资料.下面提供两个比较官方的链接,有兴趣的同学可以直 ...

  8. Qt-4.6动画Animation快速入门三字决

    Qt-4.6动画Animation快速入门三字决 Qt-4.6新增了Animation Framework(动画框架),让我们能够方便的写一些生动的程序.不必像以前的版本一样,所有的控件都枯燥的呆在伟 ...

  9. logstash Codec

    Logstash 使用一个名叫FileWatch的Ruby Gem库来监听文件变化,这个库支持glob扩展文件路径, 而且会记录一个叫.sincedb的数据库文件来跟踪被监听日志文件的当前读取位置,所 ...

  10. thinkphp中的_get,_post,_request

    ThinkPHP没有改变原生的PHP系统变量获取方式,所以依然可以通过$_GET. $_POST.$_SERVER.$_REQUEST 等方式 来获取系统变量,不过系统的Action类提供了对系统变量 ...